I have a sound file I wish to loop like the engine of a vehicle.
The sound I have is 1 second long and it is a .wav file.
I'm quite certain the file is cut to be seamless,
it loops perfectly in Audacity.
I'm calling the sound with this code:
let playEngineSound = SKAction.playSoundFileNamed("engine.wav", waitForCompletion: true)
let engineSoundLoop = SKAction.repeatActionForever(playEngineSound)
self.runAction(EngineSoundLoop, withKey: "engine sound")
I use "withKey" to be able to cancel it later.
The Sound comes out fine but there is a small but distinctive hiccup where the sound seems to be reloading or waiting for the other to stop completely before starting the new loop.
Any help would be much appreciated!
Edit: After trying the AVAudioPlayer route, it plays flawlessly, someone knows anything about the behaviour? I still want to go purely SKAction if possible.
Related
I found myself in a situation where I need to simulate audio playback to trick OS controls and MPNowPlayingInfoCenter into thinking that an audio is being played. This is because I am building a player that plays multiple audio tracks, with pauses in-between creating one, continuous "audio" track. I have already everything setup inside the app itself, and the lock screen controls are working correctly but the only problem I am facing is while the actual audio stops and a pause is being "played", the lock screen info center stops the timer, and it only continues with showing correct time and overall state once another audio track starts playing.
Here is the example of my audio track built from audio files and pause items:
let items: [AudioItem] = [
.audio("part-1.mp3"),
.pause(duration: 5), // value of type: TimeInterval
.audio("part-2.mp3"),
.pause(duration: 3),
... // the list goes on
]
then in my custom player, once AVAudioPlayer finishes its job with current item, I get the next one from the array and play either a .pause with a scheduled Timer or another .audio with AVAudioPlayer.
extension Player: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
playNextItem()
}
}
And here lies the problem, once the AVAudioPlayer stops, the Now Playing info center automatically stops too, even tho I keep feeding it fresh nowPlayingInfo. Then when it hits another .audio item, it resumes correctly and shows current time, etc.
And here lies the question
how do I trick the MPNowPlayingInfoCenter into thinking that audio is being played while I "play" my .pause item?
I realise that it may still not be clear, what I am trying to achieve but I am happy to share more insight if needed. Thanks!
Some solutions I am currently thinking about:
A. Keeping 1s long empty audio track that would play on loop for as long as the pause is needed to play.
B. Creating programatically empty audio track with appropriate lenght and playing it instead of using Timer for keeping track of pause duration/progress and relying completely on AVAudioPlayer for both .audio and .pause items. Not sure this is possible though.
C. Maybe there is a way to tell the MPNowPlayingInfoCenter that the audio keeps playing without the need of using AVAudioPlayer but some API I am not familiar with?
AVAudioPlayer is probably the wrong tool here. You want AVAudioPlayerNode, which is slightly lower-level. Create an AVAudioEngine, and attach an AVAudioPlayerNode. You can then call scheduleFile(_:at:completionHandler:) to play the audio at the times you want.
Much of the Apple documentation on AVAudioEngine appears broken right this moment, but the links hopefully will be available again shortly in the links for Audio Engine Building Blocks. (If it stays down and you have trouble finding docs, leave a comment and I'll hunt down the WWDC videos and other tutorials on using AVAudioEngine. It's not particularly difficult for simple problems.)
If you know in advance how you want to compose these items (and it looks like you may), see also AVMutableComposition, which lets you glue together assets very efficiently, including adding empty segments of silence. See Media Composition and Editing for the various tools in that space.
I'm making a simple SpriteKit game with two scenes, and I want the background music to loop unconditionally through both scenes. Right now, I'm using
if soundIsPlaying == false {
runAction(SKAction.repeatActionForever(backgroundMusicEffect), withKey: "backgroundMusic")
soundIsPlaying = true
}
in my menu scene where backgroundMusicEffect is a global variable
let backgroundMusicEffect = SKAction.playSoundFileNamed("content/divertimentoK131.mp3", waitForCompletion: true)
When I play my game, the music never loops. It always stops after one play. If I remove the if-else statement, the music plays over itself every time I reenter the menu.
Is there a better way to play background music? What am I doing wrong?
I believe that the way you are doing it, after the first time looping, every iteration after is "complete" since the sound is finished. You would need to create a new SKAction instance every time if you want to get this to loop.
This of course is a bad idea, since playSoundFileNamed is designed to only play a sound file once with as little over head as possible.
As #Alessandro Omano has commented, use SKAudioNode to get sound playing in a loop. This is my preferred way of doing in, but you limit yourself to >= iOS 9 users.
If you have to support iOS 8 users (At this point I would say why bother) then you need to look into the AVFoundation sections of the libs to get audio going, or use OpenAL.
I play back a sound like this ( this is inside a SCNNode subclass ):
let audioSource = SCNAudioSource(named: "coin.wav")
let audioPlayer = SCNAudioPlayer(source: audioSource)
self.addAudioPlayer(audioPlayer)
The first time this is called, I get a severe lag and an expection is thrown.
I notice the lag, when I disable the All_Expection_Breakpoint.
What can I do against this?
The C++ exception comes from AVAudioEngine that is used by the SceneKit audio layer. The AVAudio* framework uses C++ exceptions internally so if you have a breakpoint set in Xcode to break when C++ exceptions are thrown Xcode will break a lot in the AVAudio* code (mostly at init times). You can safely ignore these as they are caught by the framework before they reach your code anyway.
If you don't want the lag you can instantiate your audio source and load it at startup time:
let audioSource = SCNAudioSource(named: "coin.wav")
audioSource.load()
And then add the player when you need it later:
let audioPlayer = SCNAudioPlayer(source: audioSource)
self.addAudioPlayer(audioPlayer)
By the way, players are cached and recycled so you don't have to worry too much about memory being used for nothing.
Note also that the SCNAction uses exactly the same API than you do, so if you create an action with a sound that hasn't previously been loaded in memory with .load() you will also get a lag.
Hope this helps,
S.
This is really only for atmospheric, ambient and background environment sorts of sounds, to be looped and moved with a character or attached to the position of a waterfall or something like that.
It's not a performant, instant sound player for immediate sound effects requiring low latency.
For that you're better off using an SCNAction to play the audio as an Action when needed, or using something like Fmod that's designed for low latency sound playback.
I'm not sure how I know this.
Hey I have a couple of AVAudioPlayers containing one sound each. If I press the same button a couple of times, it should repeat the sound from the beginning. If I press another button afterwards, the running sound shall be stopped in order to "make room" for the new one.
The code I am using for that:
-(void) plays:(int)p{ // p is the index of the sound being triggered
if([players[p] isPlaying])
{ // setting the time back to 0 makes
players[p].currentTime = 0.0; // the player automatically play again
}
else
{
[players[p] play]; // if not playing, start playing
}
if(last!=p)
{ // if the last sound is different from the current
[players[last] stop]; // stop the last one
players[last].currentTime = 0.0;} // put its position back to 0
last=p; // set the 'last' variable
}
However, hitting the same button again ends up in a little delay (maybe 20ms) in which no sound is heard. This is the time, the AVAudioPlayer seems to need to "rewind" the track in order to play it again. One Idea to get around this would be to create multiple objects of AVAudioPlayer for each sound but that'd make some awful code! Any ideas on how to make this process quicker?
Thanks, Alex
EDIT: playing 2 different sounds works perfectly fine, I can't hear any delay in between them as I prepareToPlay all the sounds beforehand.
I know how to eliminate the 20ms gap, but first consider if you want to.
Imagine if you jumped immediately from the mid-point of the sound file to the beginning with no gap at all. Better yet, download Audacity and hear how it sounds. Because of the discontinuity, you are going to get an unpleasant crackling or pop sound. Perhaps that 1 fiftieth of a second of silence actually sounds better than immediately restarting.
If you want an uninterrupted audio stream, you're going to have to put away the easy AVAudioPlayer interface and build an AUGraph. Of course, this means learning a complex audio interface and learning all about audio formats. And then you have to figure out what data you're going to stuff into your audio stream.
How would you make your loop sounds nice? You might try fading out at the touch point, and then fading back in. Or you could search for the zero crossings at the beginning and end of your loop (zero crossings are the place where the value of your sound wave is 0. They happen all the time in a mono output, but might be harder to find if your output is stereo.) In the end will this sound nicer than the 20 ms of silence? Get out Audacity and experiment with looping before you enter the long road of learning the AUGraph interace.
To the same song, how about stopping it, then prepareToPlay and play?
I have a space shooter game I am making. I want to play a sound every time a user hits the button fire. This is a short .wav file of about half a second in length. Also, when the user dies, I have an explosion .mp3 file to play. Since the fire button can be clicked very rapidly, I need to play sounds quickly. I used some avaudio player code previously where I make a new audioplayer every time I need to fire, but this crashes occasionally if I fire very quickly. Any suggestions on ways to play sound files very quickly? (including some example code in you answer would be great)
if you using MPMediaPlayback you can try prepareToPlay