I'm working on a small project (http://www.instructables.com/id/Digispark-Volume-Control/) that uses a digispark and a rotary encoder to control media(volume,mute,pause,next,prev). The volume is controlled by the rotation of the rotary encoder and the other stuff are controlled by pressing the button in a specific way: 1 click is for pause, 2 clicks is for mute, short hold for previous song and long hold for next song.
I am asking how would i approach using c# and arduino together to control these stuff dynamically (setup and change what these events that i stated above do, and possibly if i get far with this, add something like a macro editor that uses patterns from the rotary encoders to do stuff).
example for the macro-editor like idea: if i do 2 short clicks for lets say max of 200ms and a turn to the left with the rotary encoder lock the computer.
I am trying to accomplish something like the griffin powermate(https://griffintechnology.com/us/powermate), have a software that sets up what the device does and the device to follow the software
This is the current code i got for the device, it only has the basic features for controling the media:
#include "TrinketHidCombo.h"
#define PIN_ENCODER_A 0
#define PIN_ENCODER_B 2
#define PIN_BUTTON 1
#define TRINKET_PINx PINB
static uint8_t enc_prev_pos = 0;
static uint8_t enc_flags = 0;
void setup()
{
// set pins as input with internal pull-up resistors enabled
pinMode(PIN_ENCODER_A, INPUT);
pinMode(PIN_ENCODER_B, INPUT);
pinMode(PIN_BUTTON, INPUT_PULLUP);
digitalWrite(PIN_ENCODER_A, HIGH);
digitalWrite(PIN_ENCODER_B, HIGH);
digitalWrite(PIN_BUTTON, HIGH);
TrinketHidCombo.begin(); // start the USB device engine and enumerate
// get an initial reading on the encoder pins
if (digitalRead(PIN_ENCODER_A) == LOW) {
enc_prev_pos |= (1 << 0);
}
if (digitalRead(PIN_ENCODER_B) == LOW) {
enc_prev_pos |= (1 << 1);
}
}
void loop()
{
int8_t enc_action = 0; // 1 or -1 if moved, sign is direction
// note: for better performance, the code will now use
// direct port access techniques
// http://www.arduino.cc/en/Reference/PortManipulation
uint8_t enc_cur_pos = 0;
// read in the encoder state first
if (bit_is_clear(TRINKET_PINx, PIN_ENCODER_A)) {
enc_cur_pos |= (1 << 0);
}
if (bit_is_clear(TRINKET_PINx, PIN_ENCODER_B)) {
enc_cur_pos |= (1 << 1);
}
// if any rotation at all
if (enc_cur_pos != enc_prev_pos)
{
if (enc_prev_pos == 0x00)
{
// this is the first edge
if (enc_cur_pos == 0x01) {
enc_flags |= (1 << 0);
}
else if (enc_cur_pos == 0x02) {
enc_flags |= (1 << 1);
}
}
if (enc_cur_pos == 0x03)
{
// this is when the encoder is in the middle of a "step"
enc_flags |= (1 << 4);
}
else if (enc_cur_pos == 0x00)
{
// this is the final edge
if (enc_prev_pos == 0x02) {
enc_flags |= (1 << 2);
}
else if (enc_prev_pos == 0x01) {
enc_flags |= (1 << 3);
}
// check the first and last edge
// or maybe one edge is missing, if missing then require the middle state
// this will reject bounces and false movements
if (bit_is_set(enc_flags, 0) && (bit_is_set(enc_flags, 2) || bit_is_set(enc_flags, 4))) {
enc_action = 1;
}
else if (bit_is_set(enc_flags, 2) && (bit_is_set(enc_flags, 0) || bit_is_set(enc_flags, 4))) {
enc_action = 1;
}
else if (bit_is_set(enc_flags, 1) && (bit_is_set(enc_flags, 3) || bit_is_set(enc_flags, 4))) {
enc_action = -1;
}
else if (bit_is_set(enc_flags, 3) && (bit_is_set(enc_flags, 1) || bit_is_set(enc_flags, 4))) {
enc_action = -1;
}
enc_flags = 0; // reset for next time
}
}
// Get button event and act accordingly
int b = checkButton();
if (b == 1) clickEvent();
if (b == 2) doubleClickEvent();
if (b == 3) holdEvent();
if (b == 4) longHoldEvent();
enc_prev_pos = enc_cur_pos;
if (enc_action > 0) {
TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_UP);
}
else if (enc_action < 0) {
TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_DOWN);
}
else {
TrinketHidCombo.poll(); // do nothing, check if USB needs anything done
}
}
//=================================================
// Events to trigger
void clickEvent() {
TrinketHidCombo.pressMultimediaKey(MMKEY_PLAYPAUSE);
}
void doubleClickEvent() {
TrinketHidCombo.pressMultimediaKey(MMKEY_MUTE);
}
void holdEvent() {
TrinketHidCombo.pressMultimediaKey(MMKEY_SCAN_PREV_TRACK);
}
void longHoldEvent() {
TrinketHidCombo.pressMultimediaKey(MMKEY_SCAN_NEXT_TRACK);
}
int debounce = 20; // ms debounce period to prevent flickering when pressing or releasing the button
int DCgap = 500; // max ms between clicks for a double click event
int holdTime = 1000; // ms hold period: how long to wait for press+hold event
int longHoldTime = 1500; // ms long hold period: how long to wait for press+hold event
// Button variables
boolean buttonVal = HIGH; // value read from button
boolean buttonLast = HIGH; // buffered value of the button's previous state
boolean DCwaiting = false; // whether we're waiting for a double click (down)
boolean DConUp = false; // whether to register a double click on next release, or whether to wait and click
boolean singleOK = true; // whether it's OK to do a single click
long downTime = -1; // time the button was pressed down
long upTime = -1; // time the button was released
boolean ignoreUp = false; // whether to ignore the button release because the click+hold was triggered
boolean waitForUp = false; // when held, whether to wait for the up event
boolean holdEventPast = false; // whether or not the hold event happened already
boolean longHoldEventPast = false;// whether or not the long hold event happened already
int checkButton() {
int event = 0;
buttonVal = digitalRead(PIN_BUTTON);
// Button pressed down
if (buttonVal == HIGH && buttonLast == LOW && (millis() - upTime) > debounce)
{
downTime = millis();
ignoreUp = false;
waitForUp = false;
singleOK = true;
holdEventPast = false;
longHoldEventPast = false;
if ((millis() - upTime) < DCgap && DConUp == false && DCwaiting == true) DConUp = true;
else DConUp = false;
DCwaiting = false;
}
// Button released
// DOUBLE CLICK
else if (buttonVal == HIGH && buttonLast == HIGH && (millis() - downTime) > debounce)
{
if (not ignoreUp)
{
upTime = millis();
if (DConUp == false) DCwaiting = true;
else
{
event = 2;
DConUp = false;
DCwaiting = false;
singleOK = false;
}
}
}
// Test for normal click event: DCgap expired
if ( buttonVal == LOW && (millis() - upTime) >= DCgap && DCwaiting == true && DConUp == false && singleOK == true && event != 2)
{
event = 1;
DCwaiting = false;
}
// Test for hold
if (buttonVal == HIGH && (millis() - downTime) >= holdTime) {
// Trigger "normal" hold
if (not holdEventPast)
{
event = 3;
waitForUp = true;
ignoreUp = true;
DConUp = false;
DCwaiting = false;
//downTime = millis();
holdEventPast = true;
}
// Trigger "long" hold
if ((millis() - downTime) >= longHoldTime)
{
if (not longHoldEventPast)
{
event = 4;
longHoldEventPast = true;
}
}
}
buttonLast = buttonVal;
return event;
}
Since you cannot run C# code on the Arduino itself that code needs to be running on a PC (or Raspberry Pi or similar using .NET Core).
So that means you need a way to communicate between the two. Your options are:
Send messages back and forth over a serial port (serial over USB) (easiest since you are probably already using one to program your Arduino).
Use WIFI or Ethernet and create a WebAPI endpoint that the Arduino calls into to deliver messages and to get results.
Use some other communication mechanism like Bluetooth.
Alternatively, if you have a device like the Trinket that only supports keyboard emulation over USB you would need to pick some unused keycodes for it to send and then have your application hook into the global events to handle them (see https://blogs.msdn.microsoft.com/toub/2006/05/03/low-level-keyboard-hook-in-c/) - unless of course you application is in the foreground in which case you can just read input from the keyboard as the device sends it.
Related
I have a system in which I read the serial port from a X,Y,Z motion stage, meaning that I send a signal (via usb) to a function which reads the signal, moves a stepper motor accordingly, then reads the next stepper motor signal and so on. At the moment, this function looks like this:
public void SCPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
string sCurString = "";
//Loop to receive the data from serial port
System.Threading.Thread.Sleep(150);
sCurString = SCPort.ReadExisting();
if (sCurString != "")
StrReceiver = sCurString;
if (BlnSet == true)
{
if (StrReceiver.Length == 3)
{
if (StrReceiver.Substring(StrReceiver.Length - 3) == "OK\n")
BlnReadCom = true;
}
else if (StrReceiver.Length == 4)
{
if (StrReceiver.Substring(StrReceiver.Length - 3) == "OK\n" || StrReceiver.Substring(StrReceiver.Length - 4) == "OK\nS")
BlnReadCom = true;
}
else
{
if (StrReceiver.Substring(StrReceiver.Length - 3) == "OK\n" || StrReceiver.Substring(StrReceiver.Length - 4) == "OK\nS" ||
StrReceiver.Substring(StrReceiver.Length - 5) == "ERR1\n" || StrReceiver.Substring(StrReceiver.Length - 5) == "ERR3\n" ||
StrReceiver.Substring(StrReceiver.Length - 5) == "ERR4\n" || StrReceiver.Substring(StrReceiver.Length - 5) == "ERR5\n")
BlnReadCom = true;
}
}
else
{
if (StrReceiver.Substring(StrReceiver.Length - 1, 1) == "\n")
BlnReadCom = true;
}
}
catch (Exception ex)
{
MessageBox.Show("Failed to receive data", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
This function reads the serialport SCPort, every 150ms indicated by the Thread.Sleep. If I try to read the data any faster, I get the exception (which is likely an indication of the limitation of my system). Fine. However, this exception is not thrown immediately, but every once in a while. What I would like to do instead of waiting a fixed time between reading the signal, is to each time wait until the serialport is ready and then read it. This should speed up my system, as instead of waiting 150ms between every movement, I could wait exactly the amount of time the system requires.
The question is: how do I implement this behavior in the function?
I have not tried to solve it on my own, because I really have no idea about how to do this. Will be happy to implement this into my function, but at a bare minimum I need to be pointed in the right direction.
This function handles connection to the port SCPort.
public void ConnectPort(short sPort)
{
if (SCPort.IsOpen == true)
{
ClosePort();
buttonConnectStage.Text = "Connect stage";
}
else if (SCPort.IsOpen == false)
{
SCPort.PortName = comStage.SelectedItem.ToString(); //Set the serial port number
SCPort.BaudRate = 9600; //Set the bit rate
SCPort.DataBits = 8; //Set the data bits
SCPort.StopBits = StopBits.One; //Set the stop bit
SCPort.Parity = Parity.None; //Set the Parity
SCPort.ReadBufferSize = 2048;
SCPort.WriteBufferSize = 1024;
SCPort.DtrEnable = true;
SCPort.Handshake = Handshake.None;
SCPort.ReceivedBytesThreshold = 1;
SCPort.RtsEnable = false;
//This delegate should be a trigger event for fetching data asynchronously, it will be triggered when there is data passed from serial port.
SCPort.DataReceived += new SerialDataReceivedEventHandler(SCPort_DataReceived); //DataReceivedEvent delegate
try
{
SCPort.Open(); //Open serial port
if (SCPort.IsOpen)
{
StrReceiver = "";
BlnBusy = true;
BlnSet = false;
SendCommand("?R\r"); //Connect to the controller
Delay(250);
BlnBusy = false;
if (StrReceiver == "?R\rOK\n")
{
displayValues();
BlnConnect = true; //Connected successfully
ShrPort = sPort; //Setial port number
buttonConnectStage.Text = "Disconnect stage";
SendCommand("V" + sSpeed.ToString() + "\r"); //Set speed
}
else
{
BlnBusy = false;
BlnConnect = false;
buttonConnectStage.Text = "Failed to connect";
MessageBox.Show("Failed to connect", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
}
}
catch (Exception ex)
{
}
}
}
I have trouble implementing arrow key navigation of a DataGridView (no datasource used).
The DataGridView has 2 different type of items, most of the time every second item is of the first type, vice versa the others are of the second type. Now if someone presses KeyUp / Down I want the DataGridView to jump to a row of a given index, not one up or down.
No matter how I try to solve this it is not clear when the event actually ends. If I try this inside the _previewKeyDown method:
if (DgvCarPosAndTrafficMsg.CurrentRow != null)
{
if (e.KeyCode == Keys.Down)
{
if (DgvCarPosAndTrafficMsg.SortOrder == SortOrder.Ascending)
{
for (int i = DgvCarPosAndTrafficMsg.CurrentRow.Index; i < SessionItems.Count; i++)
{
if (SessionItems[i] is CarPosItem)
{
DgvCarPosAndTrafficMsg.Rows[i].Selected = true;
break;
}
}
}
else
{
for (int i = DgvCarPosAndTrafficMsg.CurrentRow.Index; i > 0; i--)
{
if (SessionItems[i] is CarPosItem)
{
DgvCarPosAndTrafficMsg.Rows[i].Selected = true;
break;
}
}
}
}
else if (e.KeyCode == Keys.Up)
{
if (DgvCarPosAndTrafficMsg.SortOrder == SortOrder.Descending)
{
for (int i = DgvCarPosAndTrafficMsg.CurrentRow.Index; i < SessionItems.Count; i++)
{
if (SessionItems[i] is CarPosItem)
{
DgvCarPosAndTrafficMsg.Rows[i].Selected = true;
break;
}
}
}
else
{
for (int i = DgvCarPosAndTrafficMsg.CurrentRow.Index; i > 0; i--)
{
if (SessionItems[i] is CarPosItem)
{
DgvCarPosAndTrafficMsg.Rows[i].Selected = true;
break;
}
}
}
}
it would still just jump up or down 1 Row.
I have no clue when this event is actually processed automatically and I would like to have my own behaviour of key up down events. Please help me, DataGridViews in C# and their annoying events are very hard for me to track down. It seems as every event of those grids is processed differently, for some events the new state has already been applied, for others (OnSelectionChanged) it gets processed afterwards. It is documented poorly and not intuitive, I want to avoid / override all of this background stuff.
Ok I found a solution, using only one event method:
Here is my code, it is important to set the evenArgs as handled, and then do your own update method of the grid:
private void DgvCarPosAndTrafficMsg_KeyDown(object sender, KeyEventArgs e)
{
int diffDown;
int diffUp;
if (DgvCarPosAndTrafficMsg.SortOrder == SortOrder.Descending)
{
diffDown = 1;
diffUp = -2;
}
else
{
diffDown = 2;
diffUp = -1;
}
if (DgvCarPosAndTrafficMsg.CurrentRow != null)
{
if (e.KeyCode == Keys.Down && DgvCarPosAndTrafficMsg.CurrentRow.Index < DgvCarPosAndTrafficMsg.Rows.Count - diffDown)
{
DgvCarPosAndTrafficMsg.CurrentCell = DgvCarPosAndTrafficMsg.Rows[DgvCarPosAndTrafficMsg.CurrentRow.Index + diffDown].Cells[0];
}
else if (e.KeyCode == Keys.Up && DgvCarPosAndTrafficMsg.CurrentRow.Index + diffUp > 0)
{
DgvCarPosAndTrafficMsg.CurrentCell = DgvCarPosAndTrafficMsg.Rows[DgvCarPosAndTrafficMsg.CurrentRow.Index + diffUp].Cells[0];
}
}
e.Handled = true;
DgvCarPosAndTrafficMsg_UpdateAll();
}
I always jump up/down 2 rows as I expect the item of same type there, but in my UpdateAll() method I check again if it has been correct, and correct it if needed, otherwhise (if already correct) I update the visualisation of the data (and fill other grids with details of the selected entry). I hope this will help others too.
You might have -2 and + 2 for both events, I have some treatment going on afterwards so these values are my indexes I needed, adjust this according to your case or give a specific index (as seen in the question)
I'm following along with a tutorial about creating a mini-RTS in Unity, but I've hit something of a roadblock when it comes to the selection feature for assigning selection groups for multiple units.
The pertinent parts are below:
In the Update() method of my UnitsSelection class
//manage selection groups with alphanumeric keys
if (Input.anyKeyDown)
{
int alphaKey = Utils.GetAlphaKeyValue(Input.inputString);
if (alphaKey != -1)
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
_CreateSelectionGroup(alphaKey);
}
else
{
_ReselectGroup(alphaKey);
}
}
}
And the GetAlphaKeyValue method from Utils:
public static int GetAlphaKeyValue(string inputString)
{
if (inputString == "0") return 0;
if (inputString == "1") return 1;
if (inputString == "2") return 2;
if (inputString == "3") return 3;
if (inputString == "4") return 4;
if (inputString == "5") return 5;
if (inputString == "6") return 6;
if (inputString == "7") return 7;
if (inputString == "8") return 8;
if (inputString == "9") return 9;
return -1;
}
This is the code that is used in the tutorial, but to my understanding there is no way that _CreateSelectionGroup() would ever be called.
I've seen the tutorial demonstrate this functionality working, but whenever I try to run it GetAlphaKeyValue turns the Left and Right control keys into a -1 value so the if statement that checks for them never runs.
Am I missing something here? How does Unity normally handle things like Ctrl+1?
If you use the inputString I would always rather check for Contains instead of an exact string match. However, I tried to use the inputString in the past and I found it too unpredictable for most usecases ^^
While holding control keys your Keyboard most likely simply won't generate any inputString.
Only ASCII characters are contained in the inputString.
But e.g. CTRL+1 will not generate the ASCII symbol 1 but rather a "non-printing character", a control symbol - or simply none at all.
You should probably rather use e.g.
public static bool GetAlphaKeyValue(out int alphaKey)
{
alphaKey = -1;
if (Input.GetKeyDown(KeyCode.Alpha0) alphaKey = 0;
else if (Input.GetKeyDown(KeyCode.Alpha1) alphaKey = 1;
else if (Input.GetKeyDown(KeyCode.Alpha2) alphaKey = 2;
else if (Input.GetKeyDown(KeyCode.Alpha3) alphaKey = 3;
else if (Input.GetKeyDown(KeyCode.Alpha4) alphaKey = 4;
else if (Input.GetKeyDown(KeyCode.Alpha5) alphaKey = 5;
else if (Input.GetKeyDown(KeyCode.Alpha6) alphaKey = 6;
else if (Input.GetKeyDown(KeyCode.Alpha7) alphaKey = 7;
else if (Input.GetKeyDown(KeyCode.Alpha8) alphaKey = 8;
else if (Input.GetKeyDown(KeyCode.Alpha9) alphaKey = 9;
return alphaKey >= 0;
}
And then use it like
//manage selection groups with alphanumeric keys
if(Utils.GetAlphaKeyValue(out var alphaKey)
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
_CreateSelectionGroup(alphaKey);
}
else
{
_ReselectGroup(alphaKey);
}
}
As it turns out it may just be an issue with my keyboard. Different keyboards handle key presses in different ways. Mine just refuses to tell Unity that control + (some other key) are being pressed together. Changed the code to respond to Shift + (some other key) and it works fine.
This is an extended question from here Using UWP monitor live audio and detect gun-fire/clap sound
Thanks to Dernis I finally got the code working to monitor live audio and trigger events when decibel count is above a certain range.
This works perfectly when we run it in office/closed/silent area.
But when I take the app to open road, there will be traffic sound, wind sound, people talk sound and other noises and BLOW events are not identified correctly.
I would like to implement something like Lean Environment button. Before app starts monitoring, the user clicks on "Lean Environment" that recognize the sensitivity levels and set filtering to my live audio and then I start monitoring blows.
If it doesn't add too much load, I would like to record the audio to a file.
Any help on where to start would be appreciated.
OnNavigatedTo
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
//other logic
await CreateInputDeviceNodeAsync(_deviceId);
}
CreateInputDeviceNodeAsync
public async Task<bool> CreateInputDeviceNodeAsync(string deviceId)
{
Console.WriteLine("Creating AudioGraphs");
// Create an AudioGraph with default settings
AudioGraphSettings graphSettings = new AudioGraphSettings(AudioRenderCategory.Media)
{
EncodingProperties = new AudioEncodingProperties
{
Subtype = "Float",
SampleRate = 48000,
ChannelCount = 2,
BitsPerSample = 32,
Bitrate = 3072000
}
};
CreateAudioGraphResult audioGraphResult = await AudioGraph.CreateAsync(graphSettings);
if (audioGraphResult.Status != AudioGraphCreationStatus.Success)
{
_rootPage.NotifyUser("Cannot create graph", NotifyType.ErrorMessage);
return false;
}
_audioGraph = audioGraphResult.Graph;
AudioGraphSettings audioGraphSettings =
new AudioGraphSettings(AudioRenderCategory.GameChat)
{
EncodingProperties = AudioEncodingProperties.CreatePcm(48000, 2, 32),
DesiredSamplesPerQuantum = 990,
QuantumSizeSelectionMode = QuantumSizeSelectionMode.ClosestToDesired
};
_frameOutputNode = _audioGraph.CreateFrameOutputNode(_audioGraph.EncodingProperties);
_quantum = 0;
_audioGraph.QuantumStarted += Graph_QuantumStarted;
LoudNoise += BlowDetected;
DeviceInformation selectedDevice = null;
if (!string.IsNullOrWhiteSpace(_deviceId))
selectedDevice = await DeviceInformation.CreateFromIdAsync(_deviceId);
if (selectedDevice == null)
{
string device = Windows.Media.Devices.MediaDevice.GetDefaultAudioCaptureId(
Windows.Media.Devices.AudioDeviceRole.Default);
if (!string.IsNullOrWhiteSpace(device))
selectedDevice = await DeviceInformation.CreateFromIdAsync(device);
else
{
_rootPage.NotifyUser($"Could not select Audio Device {device}", NotifyType.ErrorMessage);
return false;
}
}
CreateAudioDeviceInputNodeResult result =
await _audioGraph.CreateDeviceInputNodeAsync(MediaCategory.Media, audioGraphSettings.EncodingProperties,
selectedDevice);
if (result.Status != AudioDeviceNodeCreationStatus.Success)
{
_rootPage.NotifyUser("Cannot create device output node", NotifyType.ErrorMessage);
return false;
}
_selectedMicrophone = selectedDevice.Name;
_deviceInputNode = result.DeviceInputNode;
_deviceInputNode.AddOutgoingConnection(_frameOutputNode);
_frameOutputNode.Start();
_audioGraph.Start();
return true;
}
Graph_QuantumStarted
private void Graph_QuantumStarted(AudioGraph sender, object args)
{
if (++_quantum % 2 != 0) return;
AudioFrame frame = _frameOutputNode.GetFrame();
float[] dataInFloats;
using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
using (IMemoryBufferReference reference = buffer.CreateReference())
unsafe
{
// Get the buffer from the AudioFrame
// ReSharper disable once SuspiciousTypeConversion.Global
((IMemoryBufferByteAccess) reference).GetBuffer(out byte* dataInBytes,
out var capacityInBytes);
var dataInFloat = (float*) dataInBytes;
dataInFloats = new float[capacityInBytes / sizeof(float)];
for (var i = 0; i < capacityInBytes / sizeof(float); i++)
{
dataInFloats[i] = dataInFloat[i];
}
}
double decibels = dataInFloats.Aggregate<float, double>(0f, (current, sample) => current + Math.Abs(sample));
decibels = 20 * Math.Log10(decibels / dataInFloats.Length);
_decibelList.Add(decibels);
if (double.IsInfinity(decibels) || decibels < _threshold) return;//-45
if (_watch != null && _watch.Elapsed <= TimeSpan.FromSeconds(1)) return;
LoudNoise?.Invoke(this, decibels);
_watch = Stopwatch.StartNew();
}
This is just statistics. You'll want to collect probably at least 50 frames (1 second) of data before actually having it function (maybe let the user decide by holding and releasing a button). Then you'll probably want to determine where the decibel level is usually around. I can think of 3 ways to do that.
private void Graph_QuantumStarted(AudioGraph sender, object args)
{
...
double decibels = dataInFloats.Aggregate<float, double>(0f, (current, sample) => current + Math.Abs(sample)); // I dislike the fact that the decibels variable is initially inaccurate, but it's your codebase.
decibels = 20 * Math.Log10(decibels / dataInFloats.Length);
if (scanning) // class variable (bool), you can set it from the UI thread like this
{
_decibelList.Add(decibels); // I assume you made this a class variable
}
else if (decibels == Double.NaN)
{
// Code by case below
}
else if (decibels > _sensitivity) //_sensitivity is a class variable(double), initialized to Double.NaN
{
LoudNoise?.Invoke(this, true); // Calling events is a wee bit expensive, you probably want to handle the sensitivity before Invoking it, I'm also going to do it like that to make this demo simpler
}
}
If you can control make sure there's no spike loud enough you want it to go off you can just take the max value of all those frames and say if it's over the sensitivity is maxDecibels + Math.Abs(maxDecibels* 0.2) (the decibels could be negative, hence Abs).
double maxDecibels = _decibelList.OrderByDescending(x => x)[0];
_sensitivity = maxDecibels + Math.Abs(maxDecibels* 0.2);
If you can't control when there's a spike, then you could collect those frames, sort, and have it take item [24] (of your 100 item list) and say that's the sensitivity.
sensitivity = _decibelList.OrderByDescending(x => x)[24]; // If you do a variable time you can just take Count/4 - 1 as the index
(I think it's the best but I really don't know statistics) Walk the list of frame's decibels and track the average difference in value and the what index changed it most. Afterwards, find the max value from after that index and say 75% of the change to there is the sensitivty. (Don't use a LinkedList on this)
int greatestChange, changeIndex = 0;
double p = Double.NaN; // Previous
for (int i = 0; i < _decibelList.Count(); i++)
{
if (p != Double.Nan)
{
change = Math.Abs(_decibelList[i] - p);
if (Math.Abs(change > greatestChange)
{
greatestChange = change;
changeIndex = i;
}
}
p = _decibelList[i];
}
int i = changeIndex;
p = Double.NaN; // reused
double c= Double.NaN; // Current
do
{
p = c != Double.NaN ? c : _decibelList[i];
c = _decibelList[++i];
} while (c < p);
_sensitivity = ((3 * c) + _decibelList[changeIndex]) / 4;
Note: You can (kind of) remove the need to sort by having a LinkedList and inserting in the appropiate place
Errm...Let's see...The bug my project is having is...Weird.
I'm making a Monster Training game,and when it enters on the battle mode,there is a bug that may happen,once you press the key to the left or the right of the controls,before you enter on the battle.
The bug is simple,for example,if you press A(Aka left button),when it lets you choose the action you will take,the game keeps recognizing as if the A button is still pressed,even if it isn't.
This is the code that is causing the bug:
public static int ChooseAction()
{
int choosen = 0;
Battle.CanAct = true;
Battle.ChoosenAction = -1;
while (Battle.ChoosenAction == -1)
{
if (Battle.KeyDelay == 0)
{
if (Procedures.ButtonPressed(Procedures.KeyCodes.KeyRight))
{
Battle.KeyDelay = 64;
int a = Battle.SelectedAction;
a++;
if (a >= BattleActions.Length)
{
a -= BattleActions.Length;
}
Battle.SelectedAction = a;
}
else if (Procedures.ButtonPressed(Procedures.KeyCodes.KeyLeft))
{
Battle.KeyDelay = 64;
int a = Battle.SelectedAction;
a--;
if (a < 0)
{
a += BattleActions.Length;
}
Battle.SelectedAction = a;
}
else if (Procedures.ButtonPressed(Procedures.KeyCodes.Action))
{
Battle.ChoosenAction = Battle.SelectedAction;
}
}
if (KeyDelay != 0 && !Procedures.ButtonPressed(Procedures.KeyCodes.KeyRight) && !Procedures.ButtonPressed(Procedures.KeyCodes.KeyLeft))
{
KeyDelay = 0;
}
if (Battle.KeyDelay > 0)
{
Battle.KeyDelay--;
}
}
choosen = Battle.ChoosenAction;
Battle.CanAct = false;
return choosen;
}
I don't know if this will help,but that script runs on a thread,since it's maden based on the modification of the official script,that were in c# console.
Also,the ButtonPress procedure,returns if the button is pressed,but it ever creates a new boolean everytime it is called.
Any clue of what may be causing the bug?
The Procedures.ButtonPress(KeyCodes) script.
public static bool ButtonPressed(KeyCodes buttonKey)
{
bool pressed = false;
switch (buttonKey)
{
case KeyCodes.MenuKey:
if (Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.M) || GamePad.GetState(PlayerIndex.One).IsButtonDown(Microsoft.Xna.Framework.Input.Buttons.Start))
pressed = true;
break;
case KeyCodes.RunKey:
if (Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift) || Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.RightShift) || GamePad.GetState(PlayerIndex.One).IsButtonDown(Microsoft.Xna.Framework.Input.Buttons.B))
pressed = true;
break;
case KeyCodes.KeyUp:
if (Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.W) || Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Up) || GamePad.GetState(PlayerIndex.One).IsButtonDown(Microsoft.Xna.Framework.Input.Buttons.DPadUp))
pressed = true;
break;
case KeyCodes.KeyDown:
if (Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.S) || Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Down) || GamePad.GetState(PlayerIndex.One).IsButtonDown(Microsoft.Xna.Framework.Input.Buttons.DPadDown))
pressed = true;
break;
case KeyCodes.KeyLeft:
if (Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.A) || Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Left) || GamePad.GetState(PlayerIndex.One).IsButtonDown(Microsoft.Xna.Framework.Input.Buttons.DPadLeft))
pressed = true;
break;
case KeyCodes.KeyRight:
if (Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.D) || Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Right) || GamePad.GetState(PlayerIndex.One).IsButtonDown(Microsoft.Xna.Framework.Input.Buttons.DPadRight))
pressed = true;
break;
case KeyCodes.Action:
if (Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Enter) || GamePad.GetState(PlayerIndex.One).IsButtonDown(Microsoft.Xna.Framework.Input.Buttons.A))
pressed = true;
break;
case KeyCodes.Return:
if (Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape) || GamePad.GetState(PlayerIndex.One).IsButtonDown(Microsoft.Xna.Framework.Input.Buttons.B))
pressed = true;
break;
}
return pressed;
}
After downloading XNA and setting up everything (yes I'm bored at work), I tried the code on a simple menu and it works flawlessly.
The problem is that for some reason XNA's input is not threadable. It doesn't work at all for me when I create another thread with the input. I tried various keys and none of them get a pressed state but the letter "R" which once it's pressed, it stays that way.
I also tried with the R to save the original keyboard state and once the state changes use the original to compare if R was pressed and it worked. However, you will know just ONCE that R was pressed and you won't know if it's being pressed again.
After doing all that I googled and I found recommendations to ALWAYS check your input on your main thread on the update method (as I did at first)
Hope this helps =)