Skip to content

Commit

Permalink
Merge pull request #6 from nathanntg/meters
Browse files Browse the repository at this point in the history
Input and output level meters
  • Loading branch information
nathanntg committed Nov 11, 2015
2 parents 8e06c16 + 4ae17fa commit 0ff8551
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 7 deletions.
4 changes: 4 additions & 0 deletions SyllableDetector.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
D8625C9A1BE149FE000922D8 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8625C911BE149FE000922D8 /* Common.swift */; };
D8625C9C1BE149FE000922D8 /* CircularShortTimeFourierTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8625C931BE149FE000922D8 /* CircularShortTimeFourierTransform.swift */; };
D8625CA41BE14A53000922D8 /* TPCircularBuffer.c in Sources */ = {isa = PBXBuildFile; fileRef = D8625CA21BE14A53000922D8 /* TPCircularBuffer.c */; };
D89269891BF3AEDE009482C2 /* SummaryStat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89269881BF3AEDE009482C2 /* SummaryStat.swift */; };
D8B59D161BE6B77F0099CB4B /* StreamReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B59D151BE6B77F0099CB4B /* StreamReader.swift */; };
D8D1BD481BE6D61B00059974 /* ViewControllerMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D1BD471BE6D61B00059974 /* ViewControllerMenu.swift */; };
D8D1BD4D1BE6E53C00059974 /* WindowControllerProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D1BD4C1BE6E53C00059974 /* WindowControllerProcessor.swift */; };
Expand All @@ -39,6 +40,7 @@
D8625CA21BE14A53000922D8 /* TPCircularBuffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = TPCircularBuffer.c; sourceTree = "<group>"; };
D8625CA31BE14A53000922D8 /* TPCircularBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPCircularBuffer.h; sourceTree = "<group>"; };
D8625CA51BE14B8E000922D8 /* SyllableDetector-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SyllableDetector-Bridging-Header.h"; sourceTree = "<group>"; };
D89269881BF3AEDE009482C2 /* SummaryStat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SummaryStat.swift; sourceTree = "<group>"; };
D8B59D151BE6B77F0099CB4B /* StreamReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamReader.swift; sourceTree = "<group>"; };
D8D1BD471BE6D61B00059974 /* ViewControllerMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerMenu.swift; sourceTree = "<group>"; };
D8D1BD4C1BE6E53C00059974 /* WindowControllerProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowControllerProcessor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -81,6 +83,7 @@
D8625C911BE149FE000922D8 /* Common.swift */,
D8625C8E1BE149FE000922D8 /* NeuralNet.swift */,
D8B59D151BE6B77F0099CB4B /* StreamReader.swift */,
D89269881BF3AEDE009482C2 /* SummaryStat.swift */,
D8625C8B1BE149FE000922D8 /* SyllableDetector.swift */,
D8625C8F1BE149FE000922D8 /* SyllableDetectorConfig.swift */,
D8D1BD471BE6D61B00059974 /* ViewControllerMenu.swift */,
Expand Down Expand Up @@ -185,6 +188,7 @@
D8625C9A1BE149FE000922D8 /* Common.swift in Sources */,
D8625C7F1BE14922000922D8 /* ViewControllerProcessor.swift in Sources */,
D8625C951BE149FE000922D8 /* AudioInterface.swift in Sources */,
D89269891BF3AEDE009482C2 /* SummaryStat.swift in Sources */,
D8625C7D1BE14922000922D8 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
87 changes: 87 additions & 0 deletions SyllableDetector/SummaryStat.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// SummaryStat.swift
// SyllableDetector
//
// Created by Nathan Perkins on 11/11/15.
// Copyright © 2015 Gardner Lab. All rights reserved.
//

import Foundation

protocol Stat
{
mutating func appendValue(value: Double)
func readStat() -> Double?
mutating func resetStat()
}

struct StatMean: Stat
{
var sum: Double = 0.0
var count: Int = 0

mutating func appendValue(value: Double) {
sum += value
++count
}

func readStat() -> Double? {
guard 0 < count else { return nil }
return sum / Double(count)
}

mutating func resetStat() {
sum = 0.0
count = 0
}
}

struct StatMax: Stat
{
var largest: Double?

mutating func appendValue(value: Double) {
if let cur = largest {
if value > cur {
largest = value
}
}
else {
largest = value
}
}

func readStat() -> Double? {
return largest
}

mutating func resetStat() {
largest = nil
}
}

class SummaryStat
{
private var stat: Stat
private var queue: dispatch_queue_t

init(withStat stat: Stat) {
self.stat = stat
self.queue = dispatch_queue_create("SummaryStat\(stat)", DISPATCH_QUEUE_SERIAL)
}

func writeValue(value: Double) {
dispatch_async(queue) {
self.stat.appendValue(value)
}
}

func readStatAndReset() -> Double? {
var ret: Double?
dispatch_sync(queue) {
ret = self.stat.readStat()
self.stat.resetStat()
}
return ret
}
}
72 changes: 65 additions & 7 deletions SyllableDetector/ViewControllerProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import Cocoa
import Accelerate
import AudioToolbox

struct ProcessorEntry {
Expand All @@ -31,6 +32,10 @@ class Processor: AudioInputInterfaceDelegate {
let detectors: [SyllableDetector]
let channels: [Int]

// stats
let statInput: [SummaryStat]
let statOutput: [SummaryStat]

// high duration
let highDuration = 0.001 // 1ms

Expand All @@ -55,6 +60,16 @@ class Processor: AudioInputInterfaceDelegate {
}
self.channels = channels

// setup stats
var statInput = [SummaryStat]()
var statOutput = [SummaryStat]()
for var i = 0; i < self.detectors.count; ++i {
statInput.append(SummaryStat(withStat: StatMax()))
statOutput.append(SummaryStat(withStat: StatMax()))
}
self.statInput = statInput
self.statOutput = statOutput

// setup input and output devices
interfaceInput = AudioInputInterface(deviceID: deviceInput.deviceID)
interfaceOutput = AudioOutputInterface(deviceID: deviceOutput.deviceID)
Expand Down Expand Up @@ -82,12 +97,35 @@ class Processor: AudioInputInterfaceDelegate {
let index = channels[channel]
guard index >= 0 else { return }

// get audio data
var sum: Float = 0.0
vDSP_svesq(data, 1, &sum, vDSP_Length(length))
statInput[index].writeValue(Double(sum) / Double(length))

// append audio samples
detectors[index].appendAudioData(data, withSamples: length)

// process
dispatch_async(queueProcessing) {
if self.detectors[index].seenSyllable() {
// detector
let d = self.detectors[index]

// seen syllable
var seen = false

// while there are new values
while self.detectors[index].processNewValue() {
// send to output
self.statOutput[index].writeValue(Double(d.lastOutput))

// update detected
if !seen && d.lastDetected {
seen = true
}
}

// if seen, send output
if seen {
// log
DLog("\(channel) play")

Expand All @@ -97,16 +135,32 @@ class Processor: AudioInputInterfaceDelegate {
}
}

func getOutputForChannel(channel: Int) -> Float? {
func getInputForChannel(channel: Int) -> Double? {
// valid channel
guard channel < channels.count else { return nil }

// get index
let index = channels[channel]
guard index >= 0 else { return nil }

// TODO: replace with maximum value since last call
return detectors[index].lastOutput
// output stat
if let meanSquareLevel = statInput[index].readStatAndReset() {
return sqrt(meanSquareLevel) // RMS
}

return nil
}

func getOutputForChannel(channel: Int) -> Double? {
// valid channel
guard channel < channels.count else { return nil }

// get index
let index = channels[channel]
guard index >= 0 else { return nil }

// output stat
return statOutput[index].readStatAndReset()
}
}

Expand Down Expand Up @@ -258,12 +312,16 @@ class ViewControllerProcessor: NSViewController, NSTableViewDelegate, NSTableVie

switch identifier {
case "ColumnInput", "ColumnOutput": return "Channel \(row + 1)"
case "ColumnInLevel": return NSNumber(float: 0.0)
case "ColumnInLevel":
if let p = processor {
return NSNumber(double: 100.0 * (p.getInputForChannel(row) ?? 0.0))
}
return NSNumber(double: 0.00)
case "ColumnOutLevel":
if let p = processor {
return NSNumber(float: 100.0 * (p.getOutputForChannel(row) ?? 0.0))
return NSNumber(double: 100.0 * (p.getOutputForChannel(row) ?? 0.0))
}
return NSNumber(float: 0.00)
return NSNumber(double: 0.00)
case "ColumnNetwork": return nil == processorEntries[row].config ? "Not Selected" : processorEntries[row].network
default: return nil
}
Expand Down

0 comments on commit 0ff8551

Please sign in to comment.