How to Delay a For Loop Until Sound has Finished Playing in Swift - swift

So, I have a for loop that runs values from an array into an if statement. The if statement plays a sound depending on the value in the array.
However, right now, all the values are being run through at once, so the sounds are all played at the same time. Here's what my code looks like right now:
// sortedKeys is an array made from the keys in the newData dictionary.
let sortedKeys = Array(newData.keys).sort(<)
// newData is the dictionary of type [float:float] being used to get the values that are then being run through the if statement.
for (value) in sortedKeys {
let data = newData[value]
if data <= Float(1) {
self.audioPlayer1.play()
} else if data <= Float(2) && data > Float(1) {
self.audioPlayer2.play()
} else if data <= Float(3) && data > Float(2) {
self.audioPlayer3.play()
} else if data <= Float(4) && data > Float(3) {
self.audioPlayer4.play()
} else if data <= Float(5) && data > Float(4) {
self.audioPlayer5.play()
} else if data <= Float(6) && data > Float(5) {
self.audioPlayer6.play()
} else if data <= Float(7) && data > Float(6) {
self.audioPlayer7.play()
} else if data <= Float(8) && data > Float(7) {
self.audioPlayer8.play()
} else if data <= Float(9) && data > Float(8) {
self.audioPlayer9.play()
} else {
self.audioPlayer10.play()
}
}
How can I make it so that once the AVAudioPlayer finishes playing, I continue the for loop to get the next sound? I'm thinking it has something to do with AVPlayerItemDidPlayToEndTimeNotification, but I don't know how to use this to delay the for loop.
Thanks!

How can I make it so that once the AVAudioPlayer finishes playing, then I continue the for loop to get the next sound.
Have the audio player's delegate implement -audioPlayerDidFinishPlaying:successfully: such that it selects the next sound and plays it.
Audio is played asynchronously, so you can't just have your code pause for a bit while the sound plays and then continue on. You need to design your code so that it fires off the sound, and then when the audio player says it's finished, the next sound is started.

I had a similar issue and used the sleep(insert number of seconds to pause) e.g. sleep(3) function after the self.audioPlayer.play() function in order to give the play function time to play. It is not ideal though as it blocks the main thread.

Related

How to check if the player won the game in the first attempt?

I'm developing an FPS game in Unity3D. I need to add a check to see whether the player has won the level in the first attempt.
I have the winning condition like this:
if (targetObjectsList.Count == targetObjects.Length)
{
Time.timeScale = 0f;
winUI.SetActive(true);
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
I just need to add another check inside the if, to check if it is the first attempt or not. How would I do that?
There are a few ways that you could do this. If you need the 'first attempt' to be for the current game session then a simple property on that class would suffice or a static class that holds that data.
If you need the 'first attempt' across game sessions just use playerprefs.
Call this when your level starts
var attempt = PlayerPrefs.GetInt("FirstAttempt");
// Check that it has not been set before
if (attempt != 1 || attempt != 0)
{
// First Attempt
PlayerPrefs.SetInt("FirstAttempt", 1);
} else if (attempt == 0) {
// Second attempt
}
Then in your end game function
if (PlayerPrefs.GetInt("FirstAttempt") == 1)
{
//First attempt
PlayerPrefs.SetInt("FirstAttempt", 0)
}
https://docs.unity3d.com/ScriptReference/PlayerPrefs.html

Knowing when I can skip to any point in an audio file without buffering / delay in playback

I'm loading an MP3 on my webpage using audio = new Audio(). But I'd like to know that when setting audio.currentTime, the audio can skip to any point in the file -near the end or wherever -without any delay in playback. Ie I want to know when the MP3 has downloaded in its entirety.
Can I use the Audio object/element for this, or must I use an AudioContext as shown here?
Every AudioElement is exposing its buffered data as a TimeRanges object. TimeRanges is an object which tells you how many continuous parts aka ranges are already buffered. It does also have getters which return the respective start and end of each range in seconds.
In case your AudioElement is named audio the following code snippet will log the buffered time ranges at a given point in time.
const numberOfRanges = audio.buffered.length;
for (let i = 0; i < numberOfRanges; i += 1) {
console.log(
audio.buffered.start(i),
audio.buffered.end(i)
);
}
If you want to detect the point in time at which all data is buffered you could use a check similar to this one:
const isBufferedCompletely = (audio.buffered.length === 1
&& audio.buffered.start(0) === 0
&& audio.buffered.end(0) === audio.duration);
I used the Gist referenced in the comments below to construct an example. The following snippet will periodically check if the file is already buffered. It will log a message to the console once that is the case. I tested it on Chrome (v74) and Firefox (v66) on OS X. Please note that the file can't be played at the same time as the script will set the currentTime of the Audio Element.
const audio = new Audio('http://www.obamadownloads.com/mp3s/charleston-eulogy-speech.mp3');
audio.preload = 'auto';
function detectBuffered(duration) {
// Stick with the duration once it is known because it might get updated
// when reaching the end of the file.
if (duration === undefined && !isNaN(audio.duration)) {
duration = audio.duration;
}
const isBufferedCompletely = (audio.buffered.length === 1
&& audio.buffered.start(0) === 0
&& audio.buffered.end(0) === duration);
if (isBufferedCompletely) {
const seconds = Math.round(duration);
console.log('The complete file is buffered.');
console.log(`It is about ${ seconds } seconds long.`);
} else {
// Move the playhead of the audio element to get the browser to load
// the complete file.
if (audio.buffered.length > 0) {
audio.currentTime = Math.max(0, audio.buffered.end(0) - 1);
}
setTimeout(detectBuffered, 100, duration);
}
}
detectBuffered();

can checking the hits from a raycast cause the game to crash?

so i am trying to detect if i click on a object with tag "solarsystem" and if so, then load that solarsystem onto the other scene. this code worked perfectly fine before, but now it crashes in such a way that i have to end unity from the task manager with the end task button to even close it. it just stops responding completely.
here is the code where i believe to have found the error after messing around with many Debug.log s to find where the code stopped and therefore find out where unity stops responding:
RaycastHit[] hit = Physics.RaycastAll(cursorPosition, Vector3.forward,15f);
Debug.Log("test2");//this is printed to the console - code crashes below this line
for(int i = 0; i < hit.Length; i++)
{
Debug.Log("hit"); // this is never printed to console - code crashes above this line
if(currentScene == "Universe")
{
if(hit[i].collider.gameObject.tag == "SolarSystem")
{
ChangeScene("SolarSystem");
SolarSystem clickedSolarSystem = hit[i].collider.gameObject.GetComponent<SystemObjectLink>().LinkedClass;
SolarSystem LoadedSolarSystem = SolarSystemCamera.GetComponent<SolarSystem>() as SolarSystem;
LoadedSolarSystem = clickedSolarSystem;
Debug.Log("generating system clicked on");
if (LoadedSolarSystem.preGenerated == false)
{
LoadedSolarSystem.Generate();
}
else
{
LoadedSolarSystem.Regenerate();
}
break;
}
}
if(currentScene == "SolarSystem")
{
if (hit[i].collider != null)
{
if (hit[i].collider.gameObject.tag == "Planet")
{
Target = hit[i].collider.gameObject;
break;
}
else if (hit[i].collider.gameObject.tag == "Moon")
{
Target = hit[i].collider.gameObject;
break;
}
Target = hit[i].collider.gameObject;
}
}
}
i had an for(;;) statement with a hardcoded
if(<state>){
break:
}
statement to break the infinite loop. but as i click on an object in the game. it checks the code for any errors before running it. when it does this it gets caught in the infinite loop and so to fix it i did this
for(;errorIndex<99; errorIndex++){
}
my mistake and what i learned:
never use a while(true) loop or a for loop without a way of getting out of itself (for(;;))
if the unity engine/editor ever stops responding it is because it is cought in an infinite loop- look over your code and ensure there is no possible way any of your loops can go on forever

How to offset note scheduling for interactive recording of notes via user controls

In the code below I have a note scheduler that increments a variable named current16thNote up to 16 and then looping back around to 1. The ultimate goal of the application is to allow the user to click a drum pad and push the current16thNote value to an array. On each iteration of current16thNote a loop is run on the track arrays looking for the current current16thNote value, if it finds it the sound will play.
//_________________________________________________________General variable declarations
var isPlaying = false,
tempo = 120.0, // tempo (in beats per minute)
current16thNote = 1,
futureTickTime = 0.0,
timerID = 0,
noteLength = 0.05; // length of "beep" (in seconds)
//_________________________________________________________END General variable declarations
//_________________________________________________________Load sounds
var kick = audioFileLoader("sounds/kick.mp3"),
snare = audioFileLoader("sounds/snare.mp3"),
hihat = audioFileLoader("sounds/hihat.mp3"),
shaker = audioFileLoader("sounds/shaker.mp3");
//_________________________________________________________END Load sounds
//_________________________________________________________Track arrays
var track1 = [],
track2 = [5, 13],
track3 = [],
track4 = [1, 3, 5, 7, 9, 11, 13, 15];
//_________________________________________________________END Track arrays
//_________________________________________________________Future tick
function futureTick() {
var secondsPerBeat = 60.0 / tempo;
futureTickTime += 0.25 * secondsPerBeat;
current16thNote += 1;
if (current16thNote > 16) {
current16thNote = 1
}
}
//_________________________________________________________END Future tick
function checkIfRecordedAndPlay(trackArr, sound, beatDivisionNumber, time) {
for (var i = 0; i < trackArr.length; i += 1) {
if (beatDivisionNumber === trackArr[i]) {
sound.play(time);
}
}
}
//__________________________________________________________Schedule note
function scheduleNote(beatDivisionNumber, time) {
var osc = audioContext.createOscillator(); //____Metronome
if (beatDivisionNumber === 1) {
osc.frequency.value = 800;
} else {
osc.frequency.value = 400;
}
osc.connect(audioContext.destination);
osc.start(time);
osc.stop(time + noteLength);//___________________END Metronome
checkIfRecordedAndPlay(track1, kick, beatDivisionNumber, time);
checkIfRecordedAndPlay(track2, snare, beatDivisionNumber, time);
checkIfRecordedAndPlay(track3, hihat, beatDivisionNumber, time);
checkIfRecordedAndPlay(track4, shaker, beatDivisionNumber, time);
}
//_________________________________________________________END schedule note
//_________________________________________________________Scheduler
function scheduler() {
while (futureTickTime < audioContext.currentTime + 0.1) {
scheduleNote(current16thNote, futureTickTime);
futureTick();
}
timerID = window.requestAnimationFrame(scheduler);
}
//_________________________________________________________END Scheduler
The Problem
In addition to the previous code I have some user interface controls as shown in the following image.
When a user mousedowns on a “drum pad” I want to do two things. The first is to hear the sound immediately , and the second is to push the current16thNote value to the respective array.
If I use the following code to do this a few problems emerge.
$("#kick").on("mousedown", function() {
kick.play(audioContext.currentTime)
track1.push(current16thNote)
})
The first problem is that the sound plays twice. This is because when the sound is pushed to the array it is immediately recognized by the next iteration of the note scheduler and immediately plays. I fixed this by creating a delay with setInterval to offset the push to the array.
$("#kick").on("mousedown", function() {
kick.play(audioContext.currentTime)
window.setTimeout(function() {
track1.push(note)
}, 500)
})
The second problem is musical.
When a user clicks a drum pad the value that the user anticipates the drum will be recorded at is one 16th value earlier. In other words if you listen to the metronome and click on the kick drum pad with the intent of landing right on the 1/1 down beat this won't happen. Instead, when the metronome loops back around it will have been “recorded” at one 16th increment later.
This can be remedied by writing code that intentionally offsets the value that is pushed to the array by -1 .
I wrote a helper function named pushNote to do this.
$("#kick").on("mousedown", function() {
var note = current16thNote;
kick.play(audioContext.currentTime)
window.setTimeout(function() {
pushNote(track1, note)
}, 500)
})
//________________________________________________Helper
function pushNote(trackArr, note) {
if (note - 1 === 0) {
trackArr.push(16)
} else {
trackArr.push(note - 1)
}
}
//________________________________________________END Helper
My question is really a basic one. Is there a way to solve this problem without creating these odd “offsets” ?
I suspect there is a way to set/write/place the current16thNote increment without having to create offsets to other parts of the program. But I'm hazy on what it could be.
In the world of professional audio recording there isn't a tick per 16th division value , you usually have 480 ticks per quarter note. I want to begin exploring writing my apps using this larger value but I want to resolve this "offset" issue before I go down that rabbit hole.

When pausing game(using a bool and time.timescale) it doesn't pause the score counter

this will be my first question so go on easy on me please ;)
I'm building my first game in Unity using my limited knowledge, tutorials and troubleshooting on google but i can't seem to fix this issue
i have a script that counts score(with a GUIText) and another one to pause the game(using timescale) and you probably guessed it already but when i pause the game it doesn't pause the score.
Script for Pausing:
var isPaused : boolean = false;
function Update()
{
if(Input.GetKeyDown("p"))
{
Pause();
}
}
function Pause()
{
if (isPaused == true)
{
Time.timeScale = 1;
isPaused = false;
}
else
{
Time.timeScale = 0;
isPaused = true;
}
}
Script for Score:
var Counter : int = 100000;
var Substractor : int = 0;
var Score : int = Counter - Substractor;
function Update (
) {
Score--;
guiText.text = "Score: "+Score;
}
the score script is attached to a gameobject with a gui text and the script for pausing the game is attached to the player
another issue is that when i'm moving(using the arrow keys) then press pause and then unpause the player moves faster and unpredictable for a splitsecond but this is only when i press pause while pressing the arrow buttons when i only press pause there is no issue, this is a small bugg that i'l try to fix myself, just thought i'd add it here for those that have an easy answer to this issue aswell
I only know this problem from other game frameworks and I've got no experiance with unity. But it should work the same way for unity as it does in other frameworks. You have two options:
Update is called after every rendering frame! So your timescale doesn'T influence how often update is called. Instead you have to find some dt or deltaT (don't know how it is called in unity and how to get it, but I mean the time, since unities the last call to update, please tell me in the comments, so I can edit this answer).
Then don't calculate your score like:
Score--;
but use something like:
Score -= 1 * dt
or
Score -= 1 * dt * Time.timeScale
(depending how exactly unity works)
alternatively you can sourround your update-block with some if-statement:
in Score:
function Update (
) {
if (! isPaused()) { // not sure how this function / property is called in unity
Score--;
guiText.text = "Score: "+Score;
}
}