Flow Control in Swift - AVAudioPlayer - ios

I wrote an Iphone Swift app that plays a series of sounds in a random order using AVAudioPlayer-- for now a pop sound, a horn sound and a gong. It works when you hit the play button, except....
However, when I hit the stop button nothing happens- it doesn't respond (The stop does work if I have just one sound). I believe it is due to my flow control. If I did not put in the 'while soundPlayer.playing == true {}', the code would "fly" through the sound and not wait for it to finish.
How can I modify the code so the the sound plays to completion before going to the next sound? And also allow the stop button to be functional? See Code and screen shot below. (Actually Stack overflow will not allow me to post an image since I am so new)
//
// ViewController.swift
// InsultSchool2 Swift
//
// Created by JR on 12/3/14.
// Copyright (c) 2014 JR. All rights reserved.
//
import UIKit
import AVFoundation
//---------------------------------------------------
var soundPlayer:AVAudioPlayer = AVAudioPlayer()
// used to try to do some kind of flow control. May not be needed if better way is found.
var stopFlag:Bool = false
//-----------------------------------------------------
class ViewController: UIViewController {
#IBOutlet weak var valueSliderTime: UISlider!
#IBOutlet weak var valueLabelTime: UILabel!
#IBOutlet weak var valueStepperVolume: UIStepper!
#IBOutlet weak var valueLabelVolume: UILabel!
//------------------------------------------------------
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Displays an initial Volume value in volume label on load
//Convert to an int. Otherwise you get a weird value when getting close to zero
//Multiply by 10 so the int works. Otherwise you would int'ing a number between 0.0 and 1.0.
// "\()" is a shorthand to convert whatever to a string
valueLabelVolume.text = "\(Int(valueStepperVolume.value * 10))"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//------------------------------------------------------
#IBAction func buttonStop(sender: UIBarButtonItem) {
NSLog("Enter Button Stop")
//?1 If a sound is not playing and stop is hit, then it crashes
//?2 the stop button does not work with the whlle loop below
soundPlayer.stop()
}
#IBAction func sliderTime(sender: UISlider) {
valueLabelTime.text = "\(Int(valueSliderTime.value))"
}
#IBAction func stepperVolume(sender: UIStepper) {
//Converted to an int. Otherwise you get a weird value when getting close to zero
valueLabelVolume.text = "\(Int(valueStepperVolume.value * 10))"
}
#IBAction func buttonPlay(sender: UIBarButtonItem) {
NSLog("Enter Button Start")
var soundArray:[String] = ["Sound0", "Sound1", "Sound2"]
// Randomizes a number to indicate which random sound to play in the array
/* Number is from 0 to number in the (). Don't add one or 0 will never play. Go one more than the numbers in the array. For example if you have 3 items in the array go to 3. THis will go from 0 to 2 (ie., 3 items)*/
// Reference----- var soundRandomNumber:Int = Int(arc4random_uniform(3))
var soundRandomNumber:Int
soundRandomNumber = Int(arc4random_uniform(3))
//Creates a random number to wait between sounds based on the slider value.
//arc4random requires a UInt32 (Unsigned is a positive number).
//_uniform is slightly more random than without the Uniform
//The documentation says to use Int otherwise.
println(Int(valueSliderTime.value))
NSLog("valueSliderTime.value")
var waitTimeRandomNumber = Int(arc4random_uniform(UInt32(valueSliderTime.value)))
println(waitTimeRandomNumber)
NSLog("waitTimeRandomNumber")
// Constructs a string with the random number for the URL
//var soundFile:String = soundArray[soundRandomNumber]
var soundFile:String
soundFile = soundArray[soundRandomNumber]
//Reference---- var soundURL = NSBundle.mainBundle().URLForResource(soundFile, withExtension:"mp3")
var soundURL:NSURL!
soundURL = NSBundle.mainBundle().URLForResource(soundFile, withExtension:"mp3")
soundPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
//?3 How do I set up a loop or control that works until the stop button is pressed?
while stopFlag == false{
NSLog("inside while")
println(stopFlag)
//?4 Is the below correct? The actual volume does not seem to change though the .volume does
soundPlayer.volume = Float(valueStepperVolume.value)
println(Float(valueStepperVolume.value))
NSLog("Float(valueStepperVolume.value)")
println(soundPlayer.volume)
NSLog("soundPlayer.volume")
soundRandomNumber = Int(arc4random_uniform(3))
soundFile = soundArray[soundRandomNumber]
soundURL = NSBundle.mainBundle().URLForResource(soundFile, withExtension:"mp3")
soundPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
soundPlayer.prepareToPlay()
soundPlayer.play()
//?5 How do I make the player not blow through the sound and wait until is finished
while soundPlayer.playing == true {
}
//?6 How can i make a random timer that waits for a random time before relooping?
waitTimeRandomNumber = Int(arc4random_uniform(UInt32(valueSliderTime.value)))
}// End of while loop
} //ends playButton IBAction
//?7 How to allow this app to play over other music in another player
}

use the repeat while statement instead to control the flow:
here is a link to apple's developers reference:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html
repeat {
// move up or down for a snake or ladder
square += board[square]
// roll the dice
if ++diceRoll == 7 { diceRoll = 1 }
// move by the rolled amount
square += diceRoll
} while square < finalSquare
print("Game over!")

Related

Updating Labels on a second View Controller

I have several different ViewControllers set up in my project. The first one acts as a 'landing' page and and on pressing a button the view is directed to a following ViewController.
There are various buttons and labels on there that I want to use to provide information and run a method. The buttons all work ok, but I have followed every tutorial I can to get the labels to update based on a method, and I can't seem to get them to do so.
I know the methods are called correctly as I have put print statements in there to check.
Basic idea is, program plays a series of beeps separated by a delay (bleep_time), this changes each level (bleep_level) and there are several steps (bleep_step) in each level. Ive simplified the arrays containing this data to save space.
I have successfully created a number of tutorial projects and the labels all update correctly, but they only use one ViewController.
Is the issue I am facing due to using 2 ViewControllers?
ViewController 1
import UIKit
class Bleep_Test_Menu: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
View Controller 2
import UIKit
import AVFoundation
class Bleep_Test_Level_1: UIViewController {
// Global Variables
var audioPlayer: AVAudioPlayer?
// Bleep test delays
var bleep_time = [1,2,3,4]
// Levels of bleep test
var bleep_level = [1,2,3,4]
// Level Label
#IBOutlet weak var bleepTestLevel1Level: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// Function to run repeated bleeps
#IBAction func bleepTestLevel1Start(_ sender: Any) {
var i = 0
tracker = false
let length = bleep_time.count
while i < length {
var bleeplevel = bleep_level[i]
bleepTestLevel1Level.text = "\(bleeplevel)"
playSaveSound()
sleep(UInt32(bleep_time[i]))
i += 1
}
}
// Function to play sounds
func playSaveSound(){
let path = Bundle.main.path(forResource: "Sound1.wav", ofType: nil)!
let url = URL(fileURLWithPath: path)
do {
//create your audioPlayer in your parent class as a property
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
} catch {
print("couldn't load the file")
}
}
}
The only error I keep getting in the console is this;
"Tests[53616:891478] - changing property contentsGravity in transform-only layer, will have no effect"
But I can't seem to find anything that relates to my issue.

Swift in iOS with AVAudioPlayer: Singleton not working the way I hoped it would

I am using a standard Master-Detail project, listing songs in the Master and playing them in Detail. Each song has up to four parts playing simultaneously, with independent volume control, so I have four AVAudioPlayer objects in Detail, each with a slider with an IBOutlet and an IBAction to implement the volume control.
The problem is that when you click on a song (in the list on the Master), the previous song doesn't stop. Both songs play, although the volume controls now only control the most recent song. This can go on for any number of songs.
I want to get rid of the currently playing song when a new song is clicked on.
I thought I might be able to accomplish this by creating the players inside a Singleton, in such a way that there would only ever be four players. Since, according to the documentation, each player can only play one sound file at a time, I hoped the previous sound file would stop playing when the new one started. But it's not working. The same behavior described above is still happening: multiple songs can play simultaneously, with the volume controls only controlling the most recent song. Any suggestions would be greatly appreciated.
Here is the code for the Singleton:
import Foundation
import AVFoundation
class FourPlayers {
static let audioPlayers = [one, two, three, four]
static let one = AVAudioPlayer()
static let two = AVAudioPlayer()
static let three = AVAudioPlayer()
static let four = AVAudioPlayer()
private init() {} //This prevents others from using the default '()' initializer for this class.
}
(Initially, I had just made audioPlayers static, but when that didn't work, I decided to make each individual player static as well.)
Then, in the DetailViewController:
var audioPlayers = FourPlayers.audioPlayers
Here's code for one of the four volume controls:
#IBOutlet weak var vol1: UISlider!
#IBAction func volAdjust1(sender: AnyObject) {
audioPlayers[0].volume = vol1.value
}
Playing a song looks like this (the audioFiles array is populated when the song info is passed in from the Master):
var audioFiles = []
func playAudioFiles() {
var i = 0
for _ in audioFiles {
audioPlayers[i].play()
i+=1
}
}
This is the code telling the players which file to play:
func prepareAudioFiles () {
var i = 0;
for audioFile in audioFiles {
let s = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(audioFile as? String, ofType: "mp3")!)
do {
audioPlayers[i] = try AVAudioPlayer(contentsOfURL:s)
} catch {
print("Error getting the audio file")
}
audioPlayers[i].prepareToPlay()
self.audioPlayers[i].delegate = self
}
}
It appears the the volume control is only adjusting the first player (index 0)
#IBAction func volAdjust1(sender: AnyObject) {
audioPlayers[0].volume = vol1.value
}
maybe you could add a variable to keep track of the currently playing index
var currentlyPlayingIndex = 0
#IBAction func volAdjust1(sender: AnyObject) {
audioPlayers[currentlyPlayingIndex].volume = vol1.value
}
And update it when needed
When you play the audio you could use the same variable
func playAudioFiles() {
var i = 0
for _ in audioFiles {
if i == currentlyPlayingIndex {
audioPlayers[i].play()
} else {
audioPlayers[i].stop()
}
i+=1
}
}
The problem is the following line:
var audioPlayers = FourPlayers.audioPlayers
You are actually creating a new copy of the FourPlayers.audioPlayers array into the audioPlayers variable. So you are not reusing the AVAudioPlayers but creating independent ones for each song.
See the "Assignment and Copy Behavior for Strings, Arrays, and Dictionaries" section in the Swift Programming Documentation, Classes and Structures
You could use the static variable directly in your code instead of making a copy, for example:
#IBOutlet weak var vol1: UISlider!
#IBAction func volAdjust1(sender: AnyObject) {
FourPlayers.audioPlayers[0].volume = vol1.value
}
and:
var audioFiles = []
func playAudioFiles() {
var i = 0
for _ in audioFiles {
FourPlayers.audioPlayers[i].play()
i+=1
}
}
and update the rest of your code accordingly.

How to make slider value change object or function?

I am new to the site and Swift so any feedback on question technique is gratefully received.
I am trying to make the value of a slider change both the audio that it being played and the text label (or image).
I have created my outlets:
#IBOutlet weak var audioSlider: UISlider!
#IBOutlet weak var audioValue: UILabel!
And my action:
#IBAction func audioSliderValueChanged(sender: UISlider) {
var currentValue = (sender.value)
audioValue.text = (StringInterpolationConvertible: audioValue))
All connected to one slider. I am unsure of how to approach this issue - would an if else work?
EDIT: Han Yong Code:
There was already a 'do' for other sounds within the app using AVFoundation. I imported Foundation (AVFoundation was already imported)
I have added:
#IBOutlet weak var audioSlider: UISlider!
#IBOutlet weak var audioValue: UILabel!
var audioSliderPlayer:AVAudioPlayer = AVAudioPlayer()
var currentTime: Double {
get {
return CMTimeGetSeconds(audioSliderPlayer.currentTime)
}
set {
let newTime = CMTimeMakeWithSeconds(newValue, 1)
audioSliderPlayer.seekToTime(newTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
}
}
And added the following after the do function (within viewDidLoad):
{timeObserverToken = audioSliderPlayer.addPeriodicTimeObserverForInterval(interval, queue: dispatch_get_main_queue()) {
[weak self] time in
self?.timeSlider.value = Float(CMTimeGetSeconds(time))
}}
}
I have updated the action:
#IBAction func timeSliderDidChange(sender: UISlider) {
currentTime = Double(sender.value)
}
However, I am still having issues with errors. Also, how would I be able to specify which audio it plays dependent on value?
I guess you want the audioValue.text show the same value of slider, right?
Try this:
#IBAction func audioSliderValueChanged(sender: UISlider) {
var currentValue = (sender.value)
audioValue.text = "\(currentValue)"
}
If you use AVFoundation and AVPlayer, sample source that apple provide will be helpful.
In this source, assign var that has get and set method. In set method, update your label and player state. (Sample source snippet don't update label, but you can make it.)
var currentTime: Double {
get {
return CMTimeGetSeconds(player.currentTime())
}
set {
let newTime = CMTimeMakeWithSeconds(newValue, 1)
player.seekToTime(newTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
}
}
If slider changed, it trigger variable's set method. Label and player status will be updated.
#IBAction func timeSliderDidChange(sender: UISlider) {
currentTime = Double(sender.value)
}
Add addPeriodicTimeObserverForInterval method to AVPlayer. It will be called every interval you set and trigger your variable's setter.
timeObserverToken = player.addPeriodicTimeObserverForInterval(interval, queue: dispatch_get_main_queue()) {
[weak self] time in
self?.timeSlider.value = Float(CMTimeGetSeconds(time))
}

Making a non-instantaneous change with an #IBAction button in Swift

I am trying to change the value of the speech rate property of my AVSpeechUtterance upon clicking a button in my app. I then want this value to carry over when I press the "speak" button.
If that explanation wasn't clear enough imagine this:
There are three buttons: one, two and three.
When the user presses one, the value of a the rate of an utterance changes (The same goes for the two and three).
Then after pushing one of the first three buttons, the user pushes another button that activates an utterance. This utterance will carry over the rate value and apply it to the speech.
Here is the code I have tried but doesn't work:
import UIKit
import AVFoundation
class SecondViewController: UIViewController {
#IBOutlet var minutesLabel: UITextField!
#IBOutlet var secondsField: UITextField!
func speak(sender: AnyObject) {
let speechUtterance = AVSpeechUtterance(string:exampleSpeech)
speechSynthesizer.speakUtterance(speechUtterance)
}
#IBOutlet var result: UILabel!
#IBAction func verySlow(sender: UIButton) {
let verySlowButtonPressed = true
let talkingSpeed = 90
let minutesValue : Double = (minutesLabel.text as NSString!).doubleValue
let secondsValue = (secondsField.text as NSString!).doubleValue
let secondsToMinutes = secondsValue / 60
let compiledTime = Double(minutesValue) + Double(secondsToMinutes)
let resultingWords = Double(compiledTime) * Double(talkingSpeed)
let resultCeiling = ceil(resultingWords)
result.text = "\(String(format: "%.0f", resultCeiling)) words"
if verySlowButtonPressed {
speechUtterance.rate = 0.25
speechUtterance.pitchMultiplier = 0.25
speechUtterance.volume = 0.75
}
}
#IBAction func speakButton(sender: AnyObject) {
speak(exampleSpeech)
}
In your speak function you are creating a new AVSpeechUtterance instance every time it's called. When your verySlow function is called, it's setting the rate, pitchMultiplier, and volume on what I'm presuming is a class property called speechUtterance. Since you're creating a new AVSpeechUtterance in speak each time it's called, those properties that you are setting in verySlow are not going to be set on the same instance.
One way to solve this would be to make rate, pitchMultiplier, and volume properties in your class, set those in your verySlow function, and then set them on speechUtterance after you create it in your speak function.
Something like:
var rate: Float?
var pitchMultiplier: Float?
var volume: Float?
func speak(sender: AnyObject) {
let speechUtterance = AVSpeechUtterance(string:exampleSpeech)
if let rate = self.rate {
speechUtterance.rate = rate
}
if let pitchMultiplier = self.pitchMultiplier {
speechUtterance.pitchMultiplier = pitchMultiplier
}
if let volume = self.volume {
speechUtterance.volume = volume
}
speechSynthesizer.speakUtterance(speechUtterance)
}
#IBAction func verySlow(sender: UIButton) {
// ... whatever other code you need here ...
self.rate = 0.25
self.pitchMultiplier = 0.25
self.volume = 0.75
}
Not sure but these are the value for speech rate, try using them. I hope it helps.
iOS 9
Very Slow -- 0.42
Slower -- 0.5
My Normal -- 0.53
Faster -- 0.56
Value are different for previous versions of iOS, please keep that in mind while implementing the solution.

I am getting errors such as " braced block of statements is an unused closure" and expected expression

This is my first time doing a simple project in swift and these errors are bugging me for last couple of hours. I have this code below and even though i have curly braces around and statements inside the if/else i still get that errors. Any help would be greatly appreciated guys.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var `switch`: UISwitch!
#IBOutlet var Answer: UILabel!
#IBOutlet var tempInput: UITextField!
//aqnswer value
#IBAction func switchPressed(sender: AnyObject)
{
if switch.on {
self.Answer.text = "cel to fah"
}
else {
self.Answer.text = "fah to cel"
}
}
//textfield value
#IBAction func calculate(sender: AnyObject)
{
//get user input
// value = celcius
var Value:Int = tempInput.text.toInt()!
var toFah :Int = ( 32 + Value * 9 ) / 5
//to celcius
var toCel: Int = (Value-32) * 5 / 9
if switch.on {
self.Answer.text = toFah.description
}
else {
self.Answer.text = toCel.description
}
// println(fah)
// Answer.text = fah.description
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The Swift Language Guide says:
If you need to give a constant or variable the same name as a reserved
Swift keyword, surround the keyword with back ticks (`) when using it
as a name. However, avoid using keywords as names unless you have
absolutely no choice.
In your example you have indeed a choice…
But if you really really really want to use switch as a variable name you have to wrap all occurrences of switch in back ticks.

Resources