iosswiftloopsaudiokitezaudio

How to implement a listener or while(true) to stop a waveform plot from scrolling once recorder has finished recording in Swift using AudioKit


I have a system which uses various classes from AudioKit to record the microphone input and save it to file, with a maximum duration of 30s, and simultaneously during recording the output waveform is plotted onto a waveform plot using EZAudioPlot.

My problem is that I am using a Snapchat-style recording button that begins recording/plotting on a touch down event and stops recording/plotting on a touch up (inside & outside) event - this means that when a user keeps holding the record button longer than the maximum duration (as they sometimes will do), the waveform of the microphone output keeps being plotted despite the recorder being finished. I am asking whether there is a way in Swift/AudioKit to constantly 'listen' for the recorder to stop recording, akin to something like

while true
{
    if recorder.isRecording() == false
    {
        plot.pause()
    }
}  

after the button has been pressed but before it has been released? If there were some way to listen indefinitely for the recorder to have finished recording between the button being pressed and the button being released, this would very easily solve my problem. Does such a functionality exist in some form in Swift or AudioKit?

My code is as follows (irrelevant code omitted):

import UIKit
import AudioKit
import AudioKitUI

// Define maximum recording time in seconds

let maxRecordingTime = 30.0

class ViewController: UIViewController
{
    var microphone : AKMicrophone!
    var mixer : AKMixer!
    var waveformBooster: AKBooster!
    var outputBooster : AKBooster!
    var exportTape : AKAudioFile!
    var recorder : AKNodeRecorder!
    var player : AKClipPlayer!
    var circleView : CircleView!
    var plot : AKNodeOutputPlot!

    @IBOutlet var startRecordingButton: CircularButton!
    @IBOutlet var playRecordingButton: UIButton!
    @IBOutlet var waveformPlot: EZAudioPlot!

    override func viewDidLoad()
    {
        super.viewDidLoad()

        microphone = AKMicrophone()
        mixer = AKMixer(microphone)

        // Initialise booster to set monitoring level to zero - this ensures that
        // microphone output is recorded but not sent to main audio output

        outputBooster = AKBooster(mixer)
        outputBooster.gain = 0

        // Initialise booster to set waveform display gain so that waveform can be set to display
        // only when the app is recording

        waveformBooster = AKBooster(microphone)
        waveformBooster.gain = 0

        AudioKit.output = outputBooster
        try!AudioKit.start()

        // Initialise file to store recorder output and set recorder to route mixer
        // output to file

        exportTape = try! AKAudioFile(name: "ExportTape")
        recorder = try! AKNodeRecorder(node: mixer, file: exportTape)

        recorder.durationToRecord = maxRecordingTime

        plot = AKNodeOutputPlot(waveformBooster, frame: waveformPlot.bounds)
        setupWaveformPlot()
    }

    @IBAction func startRecording(_ sender: UIButton)
    {
        // Delete contents of output file so it can be rewritten

        try! recorder.reset()

        // Perform various tasks related to getting the plot ready
        // to be rewritten to in the event of several recordings being made

        updateWaveformPlot()

        // Start the microphone and

        microphone.start()
        waveformBooster.gain = 1

        animateRecordingButton()

        do
        {
            try recorder?.record()
        } catch
        {
            AKLog("Couldn't record")
        }
    }

    @IBAction func stopRecording(_ sender: UIButton)
    {
        microphone.stop()
        waveformBooster.gain = 0
        recorder.stop()

        plot.pause()
    }

    @IBAction func playRecording(_ sender: UIButton)
    {
        let player = try! AKAudioPlayer(file: exportTape)

        if player.isStarted == false
        {
            AudioKit.output = player
            player.play()
        }
    }

    func setupWaveformPlot()
    {
        plot.plotType = .rolling
        plot.clipsToBounds = true
        plot.shouldFill = true
        plot.shouldMirror = true
        plot.color = UIColor.white
        plot.backgroundColor = UIColor.black

        // Set the gain of the plot to control range of values displayed on the waveform plot

        plot.gain = 25
        waveformPlot.addSubview(plot)
    }

    func updateWaveformPlot()
    {
        plot.clear()
        plot.resume()

        // Set rolling history length to correct value for 30s
        // such that waveform fills whole plot with no scrolling

        plot.setRollingHistoryLength(Int32(Float(1284)))
    }
}

Solution

  • I ended up implementing the behaviour I was after with a Timer, something like:

    var recordingTimer = Timer!
    let maxRecordingTime = 30.0    
    
    if recordingTimer == nil
                    {
                        recordingTimer = Timer.scheduledTimer(withTimeInterval: maxRecordingTime, repeats: false)
                        {
                            timer in
                            self.plot.pause()
                        }