iOS - Detect Blow into Mic and Convert the Results! (Swift)

iOS - Detect Blow into Mic and convert the results! (swift)

Close. You have a couple of issues. Your selector call crashes the app because you're not passing an argument and levelTimerCallback() expects one.

averagePowerPerChannel seems to give me a more real-time metering so I used that instead of peakPowerPerChannel

Also, you need to set up an audio session. I wasn't really sure what all that math was about, so I just got rid of it here. I've pasted the entire view controller below for basic mic detection.

import Foundation
import UIKit
import AVFoundation
import CoreAudio

class ViewController: UIViewController {

var recorder: AVAudioRecorder!
var levelTimer = NSTimer()
var lowPassResults: Double = 0.0

override func viewDidLoad() {
super.viewDidLoad()

//make an AudioSession, set it to PlayAndRecord and make it active
var audioSession:AVAudioSession = AVAudioSession.sharedInstance()
audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)
audioSession.setActive(true, error: nil)

//set up the URL for the audio file
var documents: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0]
var str = documents.stringByAppendingPathComponent("recordTest.caf")
var url = NSURL.fileURLWithPath(str as String)

// make a dictionary to hold the recording settings so we can instantiate our AVAudioRecorder
var recordSettings: [NSObject : AnyObject] = [AVFormatIDKey:kAudioFormatAppleIMA4,
AVSampleRateKey:44100.0,
AVNumberOfChannelsKey:2,AVEncoderBitRateKey:12800,
AVLinearPCMBitDepthKey:16,
AVEncoderAudioQualityKey:AVAudioQuality.Max.rawValue

]

//declare a variable to store the returned error if we have a problem instantiating our AVAudioRecorder
var error: NSError?

//Instantiate an AVAudioRecorder
recorder = AVAudioRecorder(URL:url, settings: recordSettings, error: &error)
//If there's an error, print that shit - otherwise, run prepareToRecord and meteringEnabled to turn on metering (must be run in that order)
if let e = error {
println(e.localizedDescription)
} else {
recorder.prepareToRecord()
recorder.meteringEnabled = true

//start recording
recorder.record()

//instantiate a timer to be called with whatever frequency we want to grab metering values
self.levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: Selector("levelTimerCallback"), userInfo: nil, repeats: true)

}

}

//This selector/function is called every time our timer (levelTime) fires
func levelTimerCallback() {
//we have to update meters before we can get the metering values
recorder.updateMeters()

//print to the console if we are beyond a threshold value. Here I've used -7
if recorder.averagePowerForChannel(0) > -7 {
print("Dis be da level I'm hearin' you in dat mic ")
println(recorder.averagePowerForChannel(0))
println("Do the thing I want, mofo")
}
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

}

Detect blow in Mic and do something

I'm using it too and it works for me. You just have to play with the value to compare it with lowPassResults. Here's my code:


- (void)levelTimerCallback:(NSTimer *)timer {
[recorder updateMeters];

const double ALPHA = 0.05;
double peakPowerForChannel = pow(10, (0.05 * [recorder peakPowerForChannel:0]));
lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;
//NSLog(@"%f", lowPassResults);
if (lowPassResults > 0.55)
NSLog(@"Mic blow detected");
}

How to detect max dB Swift

i was currently building my app about movie making,and learned something about how to metering sound level in dB.

the origin data of recorder.averagePowerForChannel is not really dB level of the sound,it's provide a level range which is -160 - 0,so we need some modification to make this data more reasonable

so i was finding some thing that makes this data(value) convert to dB level data.

(Sorry about forgot where i was found it!)

here is the code

/**
Format dBFS to dB

- author: RÅGE_Devil_Jåmeson
- date: (2016-07-13) 20:07:03

- parameter dBFSValue: raw value of averagePowerOfChannel

- returns: formatted value
*/
func dBFS_convertTo_dB (dBFSValue: Float) -> Float
{
var level:Float = 0.0
let peak_bottom:Float = -60.0 // dBFS -> -160..0 so it can be -80 or -60

if dBFSValue < peak_bottom
{
level = 0.0
}
else if dBFSValue >= 0.0
{
level = 1.0
}
else
{
let root:Float = 2.0
let minAmp:Float = powf(10.0, 0.05 * peak_bottom)
let inverseAmpRange:Float = 1.0 / (1.0 - minAmp)
let amp:Float = powf(10.0, 0.05 * dBFSValue)
let adjAmp:Float = (amp - minAmp) * inverseAmpRange

level = powf(adjAmp, 1.0 / root)
}
return level
}

i was noticed that you are recording whit 2 channels so it will be little different with my code;

wish could help you out or give you some ideas :D

LAST UPDATE

Change coding language to swift

How to record audio with a certain threshold filter in swift

I don't have much expertise in audio recordings, but I may be able to at least guide you in the right direction.


For your first question of "Starting the actual recording on a user pre-set threshold":

Audio is measured using a unit called Decibels or 'dB' for short. The threshold varies slightly between devices and microphones, so your users will have to pick a level depending on a toggle or sound they hear. For example you can provide a toggle that shows current changing audio levels and have the user slide to a certain value. You can check Discord iOS App or Decibel Meter on the App Store to get an idea.

Here is a great example in Swift showing how to track decibel levels and perform an action above a certain threshold. (You can Pause and Resume an Audio session for example) Response on this


For your second question on "How to pause and resume and collect in one file":

As I understand, an Audio session outputs everything to the same file even if you Pause and Resume it multiple time.

Apple has an excellent draft on how to handle external system interruptions like Calls for example

Post 1

Post 2


I hope I helped with your Quest

Detecting Blow through iPhone MIC in Cocos and then Performing animation on an image

I suspect the problem comes from the fact that you are calling levelTimerCallback each second. So, when a blow come in, for the whole duration of the blow your callback will make the image change.

A workaround to this would be using a BOOL flag:

@property (nonatomic) BOOL blowDetected;

In levelTimerCallback, you keep track of when a blow is detected and when it ends and you do change images only for new blows:

- (void)levelTimerCallback:(NSTimer *)timer {

[recorder updateMeters];
const double ALPHA = 0.05;
double peakPowerForChannel = pow(10, (0.05 * [recorder peakPowerForChannel:0]));
lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;
if (lowPassResults > 0.055)
{
NSLog(@"Blow detected with power: %f", lowPassResults);
if (!self.blowDetected) {
self.blowDetected = YES;
NSLog(@"Mic blow detected");
[self changeFrame];
}
} else {
NSLog(@"Blow not detected with residual power: %f", lowPassResults);
self.blowDetected = NO;
}
}

This should prevent multiple image changes for the same blow...

Now, this will work fine when between a blow and the next one you wait enough time so that the power currently detected by the mic decreases below the 0.055 threshold. This means that any blow occurring before that will be ignored.

To improve this, instead of simply filtering the signal, we could simply try and detect when the filtered value increases; so I would suggest the following implementation:

- (void)levelTimerCallback:(NSTimer *)timer {

[recorder updateMeters];
const double ALPHA = 0.05;
double peakPowerForChannel = pow(10, (0.05 * [recorder peakPowerForChannel:0]));
double currentLowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;
if (currentLowPassResults > 0.055)
{
NSLog(@"Blow detected with power: %f", lowPassResults);
if (!self.blowDetected || currentLowPassResult > K * lowPassResults) {
self.blowDetected = YES;
NSLog(@"Mic blow detected");
[self changeFrame];
}
} else {
NSLog(@"Blow not detected with residual power: %f", lowPassResults);
self.blowDetected = NO;
}
lowPassResult = currentLowPassResults;
}

You could find an optimal value for K by doing some tests.

In the latter implementation:

  1. when a blow is first detected, we change the image and go into mode "wait for blow to extinguish" (self.blowDetected == YES);

  2. in mode "wait for blow to extinguish", we do not change images, unless we detect a new blow, characterized by the fact that we have a recording power significantly larger than the current level.

Display SPL level in numeric value rather than varying colour intensity

The value you want to display is in the variable alphaFloat. You need to add a UILabel property to your ViewController, you could call it dBSPLView. Then in that place where viewController.indicatorView.backgroundColor is being changed you would do something like this:

viewController.dBSPLView.text = [NSString stringWithFormat:@"%f", alphaFloat];


Related Topics



Leave a reply



Submit