Playing 30 sound effects at the same time repeatedly - c#

I'm trying to play about 30 piano notes at the same time in my XNA application on Windows Phone 7.
I have imported and loaded the wave files like below
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
sound3 = Content.Load<SoundEffect>("1");
sound5 = Content.Load<SoundEffect>("3");
sound6 = Content.Load<SoundEffect>("4");
sound7 = Content.Load<SoundEffect>("5");
sound8 = Content.Load<SoundEffect>("6");
}
Every sound effect file is less than a second, so I'm trying to play all of them at the same time. I play them using a for loop which runs every second.(So at every loop 30 sounds will be played then it goes on and plays the same 30 sounds each second)
It works fine for a few seconds but suddenly it stops playing any sound (The loop is still working) then again starts working for once or twice and again stops working . It also sometimes makes some bad noises as if the audio system cant support too many sounds to be play at a time.
I'm not sure how I can solve the problem , if its a buffer problem or threads.

Nico Schertler is right! I've faced the same problem and fixed it by managing instances. Normally when you play soundeffect you always get the instance! By default you have to hope XNA/Monogame will care about it, but it doesn't. When you have to many live instances (even stopped, you get breaks, noise, even silence.
I hold rectangular array for all my sound effects with up to 4 instance for each, and if I have instances of a sound I want to play instead of creating I stop the oldest (by timestamp), play it and remember the current timestamp.
See the following code:
private const int MAX_INST_OF_ONE_SOUND = 4;
private SoundEffectInstance[][] sndInstArray = null;
private float[][] sndInstTimes = null;
public init()
{
sndArray = new SoundEffect[SoundsSchema.sounds.Length];
sndInstArray = new SoundEffectInstance[SoundsSchema.sounds.Length][];
sndInstTimes = new float[SoundsSchema.sounds.Length][];
for (int i = 0; i < SoundsSchema.sounds.Length; i++)
{
try
{
sndArray[i] = content.Load<SoundEffect>(SoundsSchema.sounds[i]);//SoundsSchema is string list holder class
}
catch (System.Exception)
{
}
sndInstArray[i] = new SoundEffectInstance[MAX_INST_OF_ONE_SOUND];
sndInstTimes[i] = new float[MAX_INST_OF_ONE_SOUND];
}
}
private SoundEffectInstance getValidInstance(int sound)
{
if (sound < 0 || sound > sndInstArray.Length)
return null;
SoundEffectInstance inst = null;
for (int i = 0; i < MAX_INST_OF_ONE_SOUND; i++)
{
if (sndInstArray[sound][i] == null || (sndInstArray[sound][i] != null && sndInstArray[sound][i].IsDisposed))
{
sndInstArray[sound][i] = sndArray[sound].CreateInstance();
sndInstTimes[sound][i] = MyEngine.CurTime;
inst = sndInstArray[sound][i];
break;
}
}
if (inst == null)
{
float min_time = float.MaxValue;
int ind = -1;
for (int i = 0; i < MAX_INST_OF_ONE_SOUND; i++)
{
if (sndInstArray[sound][i] != null && sndInstTimes[sound][i] < min_time)
{
min_time = sndInstTimes[sound][i];
ind = i;
}
}
if (ind == -1)
ind = 0;
if (sndInstArray[sound][ind].IsDisposed)
sndInstArray[sound][ind] = sndArray[sound].CreateInstance();
else
{
try
{
sndInstArray[sound][ind].Stop();
}
catch
{}
}
sndInstTimes[sound][ind] = MyEngine.CurTime;
inst = sndInstArray[sound][ind];
}
return inst;
}
public virtual void playSound(int sound, float volume, float panoram, bool loop)
{
if (sound < 0 || sound > sndArray.Length)
return null;
if (!mMuted && mVolume > 0)
{
SoundEffectInstance sndinst = getValidInstance(sound);
if (sndinst == null)
return null;
try
{
sndinst.IsLooped = loop;
sndinst.Pan = panoram;
sndinst.Volume = mVolume * vol;
sndinst.Play();
}
catch
{
}
}
}

Related

NAudio how to capture audio data from MMDevice as if it was playing at max volume

ive been searching for a way to try to properly capture the sound that is currently playing through the default "speakers" of the pc and make a visualizer out of it.
Thats where NAudio comes in:
MMDevice[] AudioDevices = new MMDeviceEnumerator().EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active).ToArray();
foreach (MMDevice device in AudioDevices)
{
string deviceType = device.DataFlow == DataFlow.Capture ? "INPUT" : "OUTPUT";
string deviceLabel = $"{deviceType}: {device.FriendlyName}";
deviceLabels.Add(deviceLabel);
}
var enumerator = new MMDeviceEnumerator();
var default_device = enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Console);
MMDevice selectedDevice = AudioDevices[0];
AudioDevice = selectedDevice.DataFlow == DataFlow.Render ? new WasapiLoopbackCapture(selectedDevice) : new WasapiCapture(selectedDevice, true, 10);
AudioDevice.DataAvailable += WaveIn_DataAvailable;
Capturing the audio values i use the following event:
private void WaveIn_DataAvailable(object? sender, WaveInEventArgs e)
{
int bytesPerSamplePerChannel = AudioDevice.WaveFormat.BitsPerSample / 8;
int bytesPerSample = bytesPerSamplePerChannel * AudioDevice.WaveFormat.Channels;
int bufferSampleCount = e.Buffer.Length / bytesPerSample;
if (bufferSampleCount >= AudioValues.Length)
{
bufferSampleCount = AudioValues.Length;
}
if (bytesPerSamplePerChannel == 2 && AudioDevice.WaveFormat.Encoding == WaveFormatEncoding.Pcm)
{
for (int i = 0; i < bufferSampleCount; i++)
AudioValues[i] = BitConverter.ToInt16(e.Buffer, i * bytesPerSample);
}
else if (bytesPerSamplePerChannel == 4 && AudioDevice.WaveFormat.Encoding == WaveFormatEncoding.Pcm)
{
for (int i = 0; i < bufferSampleCount; i++)
AudioValues[i] = BitConverter.ToInt32(e.Buffer, i * bytesPerSample);
}
else if (bytesPerSamplePerChannel == 4 && AudioDevice.WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
{
for (int i = 0; i < bufferSampleCount; i++)
AudioValues[i] = BitConverter.ToSingle(e.Buffer, i * bytesPerSample);
}
else
{
throw new NotSupportedException(AudioDevice.WaveFormat.ToString());
}
}
Lastly, I then use a FFT library to then convert the audio values to a new array using FFTmagnitude Transformation and then process them further to get my visualizer but this is out of scope of the problem im facing.
The problem i have is that the audio data array outputed from the WaveIn_DataAvailable event is dependant of the "speakers" volume, so if the volume is for example at 2%, the audio values im getting are much lower than if the volume was at 100%.
Strangely if the volume is muted or 0% the audio values are as high as 100%.
What i would like to get instead is, regardless of the volume level to always get the audio values as if it was 100%.
I couldnt find any way to get the audio values this way, there has to be something very obvious or simple that im missing but so far i couldnt find any example or suggestion on how to achieve this.

Factorio Style Power Line in Unity

TLDR; How would you make power lines without infinite recursion?
I am currently working on a game where you place buildings and power lines between them to transfer power from coal factories to buildings that need power. Is there an algorithm or specific way of programming power lines so that they can transfer power between themselves and to and from other buildings. The main problem is doing this without having energy build up and exponentially increase. Considering there can be any number of power lines connected to any other amount, this is difficult.
Any tips or help?
private void GetTotalConnections(GameObject line, List<GameObject> checker) // HOLY SHIT IT WORKS HOLY SHIT IT WORKS HOLY SHIT IT WORKS
{
PowerLine temp = line.GetComponent<PowerLine>();
if (checker.Contains(line))
{
if (CheckIfConnectionsContains(checker, line.GetComponent<PowerLine>()) == true)
{
return;
}
}
for (int i = 0; i < temp.connectedLines.Count; i++)
{
if (checker.Contains(temp.connectedLines[i]) == false)
{
checker.Add(temp.connectedLines[i]);
GetTotalConnections(temp.connectedLines[i], checker);
}
else // if its already in the list, skip this entry since we already checked that it still had some new connections
{
continue;
}
}
}
private bool CheckIfConnectionsContains(List<GameObject> list, PowerLine line)
{
int count = 0;
for (int i = 0; i < line.connectedLines.Count; i++)
{
if (list.Contains(line.connectedLines[i]))
{
count++;
}
}
if (count == line.connectedLines.Count) // if every connection is already in the list
{
return true;
}
return false;
}

Using Audio Graph, learn environment noise and filter in a UWP App

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

Application freezes just after goin back from desktop to app (on android device)

It tooks a lot of time to unfreeze (but sometimes it just crashes).
I was debugging whole code to find a place where the problem is and I found that there's something wrong while I was saving things to Player Prefs.
My "idea" why it is causing. Multiple times getting and setting prefs with different values.
Test code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class test : MonoBehaviour
{
[SerializeField]
private Image bg;
[SerializeField]
private Text text;
//private void Start()
//{
// PlayerPrefs.SetString("dd", "x");
//}
//Update is called once per frame
//bool was = false;
int counter = 0;
System.DateTime lastUpdate;
bool active = false;
List<string> tmp = new List<string>() { "5", "7" };
private void Start()
{
lastUpdate = System.DateTime.UtcNow;
}
void Update()
{
System.DateTime now = System.DateTime.UtcNow;
System.TimeSpan diff = now - lastUpdate;
text.text = diff.ToString();
if (diff.TotalSeconds > 10)
{
active = !active;
lastUpdate = now;
}
if (active)
{
bg.color = Color.green;
//if ((System.DateTime.UtcNow - xyz).TotalSeconds > 5)
//if (counter > 5)
//{
//if (!was)
//{
// Debug.Log("XX_update");
//}
//was = true;
//string x = PlayerPrefs.GetString("dd");
for (int i = 0; i < 20; i++)
{
string x = PlayerPrefs.GetString("dd");
Set(5, i);
//string x = PlayerPrefs.GetString(CommonMethods.RandomString(5));
}
//}
//else
//{
// was = false;
// counter++;
//}
}
else
{
bg.color = Color.red;
}
}
private System.DateTime xyz = System.DateTime.UtcNow;
public void OnApplicationFocus(bool focus)
{
counter = 0;
Debug.Log("XX_onappfocus");
xyz = System.DateTime.UtcNow;
}
public void Set(double value, int x = 0)
{
//PlayerPrefs.SetString(name, value.ToString("E3"));
//string costam = random.NextDouble().ToString();
//PlayerPrefs.SetString(name, costam);
//UnityEngine.Debug.Log("XX_" + costam);
//List<string> tmp = new List<string>() { "98754.38238715351", "98754.38238415351" };
//string result = tmp.RandomElement();
string result = tmp[x % 2];
//PlayerPrefs.SetString(name, result);
PlayerPrefs.SetString("dd", result);
//PlayerPrefs.SetString(name, "1.054E+003");
}
}
I was thinking that maybe there's a problem with double's precision, but it wasn't. Then I checked if it is caused by parsing inside setstring methods, but it wasn't. Problem isn't connected with a key, so it can be random.
If you want to check this try to build this code with 2 different values in tmp string list (now it's 5 and 7) and there will be bug, then change these values to the same (f.e. 5 and 5). Problem will disappear. Also you can delete get or set from FOR loop and problem will also disappear.
I also made a tricky switch inside Update method to check if it is connected with this get / set pair so you can check it too.
Sorry for my english guys but this problem is so complex that I even don't know how to describe it. I am trying to solve it for 3 days (we need to solve this or make a 'walk around' because we can't use that app without this).
Thanks in advance.

How to have an Coroutine Act like an Update

I have been told I should not be using Updates when I don't need to use them. I am not sure when to not use them, but I am trying to figure it out.
I want to have an event, "Wave", run for 20 seconds and then switch to a random "Wave" and run for another 20 seconds and keep repeating this cycle.
Game Manager:
void Start()
{
StartFireWaves();
}
public void StartFireWaves()
{
StartCoroutine(wm.SelectWave());
}
Wave Manager:
public IEnumerator SelectWave()
{
float waitFor;
float diff = 0;
diff = nextWaveAT - Time.time;
if (diff <= 0f)
{
SpawnFlames();
waitFor = Random.Range(15, 20);
}
nextWaveAT = nextWaveAT + waitFor;
yield return new WaitForSeconds(waitFor);
}
SpawnFlames:
void SpawnFlames()
{
float val = Random.value;
if (val < .25f)
{
Wave1();
}
if (val >= .25f && val < .5f)
{
Wave2();
}
if (val >= .5f && val < .75f)
{
Wave3();
}
else
{
Wave4();
}
}
I am assuming this is how it should work, but it doesn't seem to keep "updating" or running. It just runs once and then nothing happens.
Use Coroutine with multiple nested while loops to do this. The first while loop is to make it run forever until stopRunningWaves() function is called. The second while loop is used to run the wave for x amount of time then jumps back to the first while loop.
bool keepRunningWaves = false;
IEnumerator startingRunningWaves(float second = 20)
{
if (keepRunningWaves)
{
yield break;
}
keepRunningWaves = true;
while (keepRunningWaves)
{
float timer = 0;
int randWave = Random.Range(1, 5);
while (timer < second)
{
if (!keepRunningWaves)
{
yield break;
}
if (randWave == 1)
{
Debug.Log("Running Wave 1");
Wave1();
}
else if (randWave == 2)
{
Debug.Log("Running Wave 2");
Wave2();
}
else if (randWave == 3)
{
Debug.Log("Running Wave 3");
Wave3();
}
else if (randWave == 4)
{
Debug.Log("Running Wave 4");
Wave4();
}
timer += Time.deltaTime;
yield return null;
}
//Reset Timer for next run
timer = 0;
yield return null;
}
keepRunningWaves = false;
}
void stopRunningWaves()
{
keepRunningWaves = false;
}
To test it, use 3 seconds to do it so that you will save your time:
StartCoroutine(startingRunningWaves(3));

Categories