AudioKit, AKPlayer: How to play from Samples, not time - ios

I am currently using FDWaveFormView to great success to display waveforms representing audio I record from AKMicrophone or AKAudioFile.
I am successfully able to highlight specific regions in the waveform and FDwaveForm gives back a range of the samples from the audiofile.
My problem now is I cannot find an appropriate method in AKPlayer that would let me play from a start sample to an end sample.
I noticed that AKSamplePlayer is now deprecated, but it did have a method:play(from: Sample, to: Sample)
My guess is that I would be able to do some math to translate Sample position to time (as a Double as prescribed in AKPlayer), however I have not found the appropriate math or functions to do this, any hints?
To be very explicit in what I am trying to do, please refer to the image below:
note for any AudioKit core members who may see this question, I know there are a variety of AudioKitUI components that may of made this easier, however only FDWaveFormView has given me the functionality I need for this particular app, i'm happy to discuss further offline, thanks again so much.
EDIT
I've come up with some code that I believe has solved it:
let startingSampleIndex = self.waveformPlot.highlightedSamples!.min()
let endingSampleIndex = self.waveformPlot.highlightedSamples!.max()
let millisecondsPerSample : Double = 1000 / 44100
let startingDuration : Double = (startingSampleIndex! * millisecondsPerSample) / 1000
let endingDuration : Double = (endingSampleIndex! * millisecondsPerSample) / 1000
print("StartSample:\(startingSampleIndex!) | EndSample:\(endingSampleIndex!) | milliPerSample:\(millisecondsPerSample) | StartDuration:\(startingDuration) | EndDuration:\(endingDuration)")
player.play(from: startingDuration, to: endingDuration)
The main equation being numberOfSamples * millisecondsPerSample = timeInMilliseconds by dividing by 1000 I can normalize everything to seconds which is what AKPlayer wants. If anyone sees something problematic here I'd love the advice but I think this has done it! Sorry I am still new to DSP and so thankful for AudioKit being an incredible Shepard into this world!

To convert from frames to seconds you should divide by the sample rate of the audio file, not a hardcoded 44100 value:
guard let frameRange = self.waveformPlot.highlightedSamples else { return }
let startTime = frameRange.min() / audioFile.fileFormat.sampleRate
let endTime = frameRange.max() / audioFile.fileFormat.sampleRate
player.play(from: startTime, to: endTime)

I found the solution, essentially RTFM on DSP 101 and samples 😅:
let startingSampleIndex = self.waveformPlot.highlightedSamples!.min()
let endingSampleIndex = self.waveformPlot.highlightedSamples!.max()
let millisecondsPerSample : Double = 1000 / 44100
let startingDuration : Double = (startingSampleIndex! * millisecondsPerSample) / 1000
let endingDuration : Double = (endingSampleIndex! * millisecondsPerSample) / 1000
print("StartSample:\(startingSampleIndex!) | EndSample:\(endingSampleIndex!) | milliPerSample:\(millisecondsPerSample) | StartDuration:\(startingDuration) | EndDuration:\(endingDuration)")
player.play(from: startingDuration, to: endingDuration)
This is working excellently, thanks again to both FDWaveFormView and AudioKit!

Related

Using AKMixer with volume lower 0.00001 there is no output

We are using two AKMixer (one for left, one for right channel) and one AKMixer as output with these two mixers as inputs.
If one of the mixers has a volume lower than 0.00001 the output signal is lost. But lower volumes are possible, because if we lower the main system volume on values over 0.00001 the signal on the headphone-jack is going lower.
As a workaround I tried to set the AKMixer.output.volume to 0.5 and the input mixers to 0.00001 and it works too. But in my application I also need max output and than I got weird "clicks" when changing the both volume levels at once.
It would be great if somebody can help. With the workaround or the causing problem.
Thanks.
var rightSine = AKOscillator(waveform: AKTable(.sine))
var rightPanner : AKMixer!
let pan2 = AKPanner(self.rightSine, pan: 1)
pan2.rampDuration = 0
let right1: AKMixer = AKMixer(pan2 /*, .... some more */)
self.rightPanner = right1
let mix = AKMixer(self.rightPanner /* left channel... */)
mix.volume = 1.0
AudioKit.output = mix
do {
try AudioKit.start()
} catch {
}
self.rightPanner.volume = 0.00002
This is the code used to initialise the audio stuff (shortened) and afterwards the nodes are started.
*Edit: I'm testing the precise threshold on which the output is broken..
AudioKit's AKMixer is a simple wrapper around Apple's AVAudioMixerNode and as such, I can't really dig much deeper to help you solve the problem using that node. But, if you're willing to switch to AKBooster, whose job it is to amplify or diminish a signal, I think you will be fine to use small numbers for your gain value.
var rightSine = AKOscillator(waveform: AKTable(.sine))
var rightBooster: AKBooster!
let pan2 = AKPanner(self.rightSine, pan: 1)
pan2.rampDuration = 0
let right1: AKMixer = AKMixer(pan2 /*, .... some more */)
self. rightBooster = AKBooster(right1)
let mix = AKMixer(self. rightBooster /* left channel... */)
mix.volume = 1.0
AudioKit.output = mix
self.rightBooster.gain = 0.00002

AudioKit: Way to inject silence/fadeout with a loop with AKPlayer?

In my app, I give the user the option to play a small frame of audio (from a larger audio file)in order to listen over and over to do a manual transcription. AKPlayer makes this trivial. Now, because the frame of audio is pretty small, it's pretty intense to hear this loop over and over (a little maddening in the classical sense of the word). I'd like to either fade it out/fade it back in with the loop OR just inject like 500 ms of silence before the loop starts again. I have no idea where to start, here is the current working code as is:
public func playLoop(start: Double, end: Double) {
self.chordLoopPlayer.isLooping = true
self.chordLoopPlayer.buffering = .always
self.chordLoopPlayer.preroll()
let millisecondsPerSample : Double = 1000 / 44100
let startingDuration : Double = (((start * millisecondsPerSample) / 1000) / 2)
let endingDuration : Double = (((end * millisecondsPerSample) / 1000) / 2)
print("StartinDuration:\(startingDuration) | EndingDuration:\(endingDuration)")
self.chordLoopPlayer.loop.start = startingDuration
self.chordLoopPlayer.loop.end = endingDuration
self.chordLoopPlayer.play(from: startingDuration, to: endingDuration)
Thanks so much <3
You just need to set .fade values for your fade-in/fade-out prior to calling the play() function. AudioKit will execute them each time going in and out of the loop. So assuming you'd like a 2-second fade-out, and a 2-second fade-in (adjust to your taste), your code would look like:
public func playLoop(start: Double, end: Double) {
self.chordLoopPlayer.isLooping = true
self.chordLoopPlayer.buffering = .always
self.chordLoopPlayer.preroll()
let millisecondsPerSample : Double = 1000 / 44100
let startingDuration : Double = (((start * millisecondsPerSample) / 1000) / 2)
let endingDuration : Double = (((end * millisecondsPerSample) / 1000) / 2)
print("StartinDuration:\(startingDuration) | EndingDuration:\(endingDuration)")
self.chordLoopPlayer.loop.start = startingDuration
self.chordLoopPlayer.loop.end = endingDuration
// add fade in/out values to fade in or fade out during playback; reset to 0 to disable.
self.chordLoopPlayer.fade.inTime = 2 // in seconds
self.chordLoopPlayer.fade.outTime = 2 // in seconds
self.chordLoopPlayer.play(from: startingDuration, to: endingDuration)
}
I find the AudioKit documentation a bit frustrating in this respect, as it's not super-easy to find these properties if you don't already know what you're looking for, or to understand how to use them if you haven't already come across sample code, so I hope this is a useful example for others who happen to search on this topic on SO. In any case, the list of sub-properties associated with AudioKit's .fade property is here: https://audiokit.io/docs/Classes/AKPlayer/Fade.html

How to get the accurate time position of a live streaming in avplayer

I'm using AVPlayer to play a live streaming. This stream supports one hour catch-up which means user can seek to one hour ago and play. But I have one question how do I know the accurate position that the player is playing. I need to display current position on the player view. For example,if user is playing half an hour ago then display -30:00; if user is playing the latest content, the player will show 00:00 or live. Thanks
Swift solution :
override func getLiveDuration() -> Float {
var result : Float = 0.0;
if let items = player.currentItem?.seekableTimeRanges {
if(!items.isEmpty) {
let range = items[items.count - 1]
let timeRange = range.timeRangeValue
let startSeconds = CMTimeGetSeconds(timeRange.start)
let durationSeconds = CMTimeGetSeconds(timeRange.duration)
result = Float(startSeconds + durationSeconds)
}
}
return result;
}
To get a live position poison and seek to it you can by using seekableTimeRanges of AVPlayerItem:
CMTimeRange seekableRange = [player.currentItem.seekableTimeRanges.lastObject CMTimeRangeValue];
CGFloat seekableStart = CMTimeGetSeconds(seekableRange.start);
CGFloat seekableDuration = CMTimeGetSeconds(seekableRange.duration);
CGFloat livePosition = seekableStart + seekableDuration;
[player seekToTime:CMTimeMake(livePosition, 1)];
Also when you seek some time back, you can get current playing position by calling currentTime method
CGFloat current = CMTimeGetSeconds([self.player.currentItem currentTime]);
CGFloat diff = livePosition - current;
I know this question is old, but I had the same requirement and I believe the solutions aren't addressing properly the intent of the question.
What I did for this same requirement was to gather the current point in time, the starting time, and the length of the total duration of the stream.
I'll explain something before going further, the current point in time could surpass the (starting time + total duration) this is due to the way hls is structured as ts segments. Ts segments are small chucks of playable video, you could have on your seekable range 5 ts segments of 10 seconds each. This doesn't mean that 50 secs is the full length of the live stream, there is around a full segment more (so 60 seconds of playtime total) but it isn't categorized as seekable since you shouldn't seek to that segment. If you were to do this you'll notice in most instances rebuffering (cause the source may be still creating the next ts segment when you already reached the end of playback).
What I did was checking if the current stream time is further than the seekable rage, if so this would mean were are live on stream. If it isn't you could easily calculate how far behind you are from live if you subtract the current time, starting time, and total duration.
let timeRange:CMTimeRange = player.currentItem?.seekableTimeRanges.last
let start = timeRange.start.seconds
let totalDuration = timeRange.duration.seconds
let currentTime = player.currentTime().seconds
let secondsBehindLive = currentTime - totalDuration - start
The code above will give you a negative number with the number of seconds behind "live" or more specifically the start of the lastest ts segment. Or a positive number or zero when it's playing the latest ts segment.
Tbh I don't really know when does the seekableTimeRanges will have more than 1 value, it has always been just one for the streams I have tested with, but if you find in your streams more than 1 value you may have to figure if you want to add all the ranges duration, which time range to use as the start value, etc. At least for my use case, this was enough.

SKEmiterNode with AVAudioPlayer for music visuals

PLEASE SOMEONE HELP!
I want to have my SKEmiterNode's scale(meaning size) get larger and smaller to the music i have built into the application using AVAudioPlayer. Right now this is pretty much all I have for the SKEmiterNode and it looks great:
beatParticle?.position = CGPoint(x: self.size.width * 0.5, y: self.size.height * 0.5)
var beatParticleEffectNode = SKEffectNode()
beatParticleEffectNode.addChild(beatParticle!)
self.addChild(beatParticleEffectNode)
All the looks are done in the .sks file.
Here is where I call the "updateBeatParticle" function in a continual loop so that It can where i will put my code for making the particle's scale(meaning size) larger and smaller to the music.
var dpLink : CADisplayLink?
dpLink = CADisplayLink(target: self, selector: "updateBeatParticle")
dpLink?.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
func updateBeatParticle(){
//Put code here
}
Any idea how i can do this? I looked at some tutorials such as this: https://www.raywenderlich.com/36475/how-to-make-a-music-visualizer-in-ios
However, i can't quite get my head around it because they're using an emitterLayer and its in Obj-C and am also interested in any other ideas you wonderful people may have!
WARNING: The following code has not been tested. Please let me know if it works.
Firstly, it looks like you are using SpriteKit, therefore you could put the code needed to alter the emitter scale in the SKScene method update:, which automatically gets called virtually as often as a CADisplayLink.
Essentially all you need to do is update the emitter scale in the update: method based on the volume of the channels of your AVAudioPlayer. Note that the audio player may have multiple channels running, so you need to average out the average power for each.
Firstly...
player.meteringEnabled = true
Set this after you initialise your audio player, so that it will monitor the levels of the channels.
Next, add something like this in your update method.
override func update(currentTime: CFTimeInterval) {
var scale: CGFloat = 0.5
if audioPlayer.playing { // Only do this if the audio is actually playing
audioPlayer.updateMeters() // Tell the audio player to update and fetch the latest readings
let channels = audioPlayer.numberOfChannels
var power: Float = 0
// Loop over each channel and add its average power
for i in 0..<channels {
power += audioPlayer.averagePowerForChannel(i)
}
power /= Float(channels) // This will give the average power across all the channels in decibels
// Convert power in decibels to a more appropriate percentage representation
scale = CGFloat(getIntensityFromPower(power))
}
// Set the particle scale to match
emitterNode.particleScale = scale
}
The method getIntensityFromPower is used to convert the power in decibels, to a more appropriate percentage representation. This method can be declared like so...
// Will return a value between 0.0 ... 1.0, based on the decibels
func getIntensityFromPower(decibels: Float) -> Float {
// The minimum possible decibel returned from an AVAudioPlayer channel
let minDecibels: Float = -160
// The maximum possible decibel returned from an AVAudioPlayer channel
let maxDecibels: Float = 0
// Clamp the decibels value
if decibels < minDecibels {
return 0
}
if decibels >= maxDecibels {
return 1
}
// This value can be adjusted to affect the curve of the intensity
let root: Float = 2
let minAmp = powf(10, 0.05 * minDecibels)
let inverseAmpRange: Float = 1.0 / (1.0 - minAmp)
let amp: Float = powf(10, 0.05 * decibels)
let adjAmp = (amp - minAmp) * inverseAmpRange
return powf(adjAmp, 1.0 / root)
}
The algorithm for this conversion was taken from this StackOverflow response https://stackoverflow.com/a/16192481/3222419.

Example of AudioStreamBasicDescription for 16hz, 16bit mono format as WAV file

I'm having trouble with creating a description for the above format and have tried a number of things but keep getting an invalid param when trying to record.
Does anyone have an example of how to create to this format?
var audioFileOutputDescription : AudioStreamBasicDescription = AEAudioStreamBasicDescriptionNonInterleaved16BitStereo
audioFileOutputDescription.mFormatID = kAudioFormatLinearPCM
audioFileOutputDescription.mSampleRate = 16000.0
audioFileOutputDescription.mBitsPerChannel = 16
audioFileOutputDescription.mChannelsPerFrame = 1
audioFileOutputDescription.mFramesPerPacket = 1
audioFileOutputDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger |
kAudioFormatFlagsNativeEndian
Thanks.
You declare AEAudioStreamBasicDescriptionNonInterleaved16BitStereo which is for 2 channels, then set mChannelsPerFrame = 1, which is for 1 channel. That conflict in the number of channels might be the problem.
If that isn't the problem, one method to debug this is to start with a working asbd (from one of Apple's sample apps or guides), then change one thing at a time to find the unsupported parameter or parameter combination.
Also, WAV files are little endian.
I don't see mBytesPerPacket or mBytesPerFrame set anywhere.
audioFileOutputDescription.mBytesPerFrame = 2; // 1 mono channel * 2 bytes per channel
You may need to add a converting audio unit as i find audio units don't support all the variations possible variations within an ASBD.
input -> converter AU -> do your magic AU -> convertback AU -> output
I think one of the biggest difficulties with audio units is ASBD's, each audio unit type supports only a number of formats, and as #hotpaw2 suggests, changing one parameter and testing is sometimes the only way to get to the bottom of the issue. I also find splitting audio units up into small individual audio units to do a single task works well as they become light weight and re-usable for the future.
Works fine for me:
fmt.mFormatID = kAudioFormatLinearPCM; // 2
fmt.mSampleRate = 8000.0; // 3
fmt.mChannelsPerFrame = 1; // 4
fmt.mBitsPerChannel = 16; // 5
fmt.mChannelsPerFrame = 1;
fmt.mFramesPerPacket = 1;
fmt.mBytesPerFrame = 2;
fmt.mBytesPerPacket = 2;
// 7
fmt.mFormatFlags = // 9
kLinearPCMFormatFlagIsBigEndian
| kLinearPCMFormatFlagIsSignedInteger
| kLinearPCMFormatFlagIsPacked;

Resources