@siteed/expo-audio-stream 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/android/build.gradle +5 -0
- package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +120 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +34 -4
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +635 -0
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +194 -79
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +1 -0
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +48 -2
- package/android/src/main/java/net/siteed/audiostream/FFT.kt +44 -0
- package/android/src/main/java/net/siteed/audiostream/Features.kt +56 -0
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +12 -0
- package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +56 -0
- package/app.plugin.js +1 -1
- package/build/AudioRecorder.provider.js +1 -1
- package/build/AudioRecorder.provider.js.map +1 -1
- package/build/ExpoAudioStream.native.d.ts +3 -0
- package/build/ExpoAudioStream.native.d.ts.map +1 -0
- package/build/ExpoAudioStream.native.js +6 -0
- package/build/ExpoAudioStream.native.js.map +1 -0
- package/build/ExpoAudioStream.types.d.ts +79 -6
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +41 -0
- package/build/ExpoAudioStream.web.d.ts.map +1 -0
- package/build/ExpoAudioStream.web.js +184 -0
- package/build/ExpoAudioStream.web.js.map +1 -0
- package/build/ExpoAudioStreamModule.d.ts +2 -2
- package/build/ExpoAudioStreamModule.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.js +12 -3
- package/build/ExpoAudioStreamModule.js.map +1 -1
- package/build/WebRecorder.d.ts +47 -0
- package/build/WebRecorder.d.ts.map +1 -0
- package/build/WebRecorder.js +243 -0
- package/build/WebRecorder.js.map +1 -0
- package/build/index.d.ts +14 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +106 -7
- package/build/index.js.map +1 -1
- package/build/inlineAudioWebWorker.d.ts +3 -0
- package/build/inlineAudioWebWorker.d.ts.map +1 -0
- package/build/inlineAudioWebWorker.js +340 -0
- package/build/inlineAudioWebWorker.js.map +1 -0
- package/build/useAudioRecording.d.ts +24 -9
- package/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +107 -29
- package/build/useAudioRecording.js.map +1 -1
- package/build/utils.d.ts +31 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +143 -0
- package/build/utils.js.map +1 -0
- package/expo-module.config.json +13 -4
- package/ios/AudioAnalysisData.swift +39 -0
- package/ios/AudioProcessingHelpers.swift +59 -0
- package/ios/AudioProcessor.swift +317 -0
- package/ios/AudioStreamError.swift +7 -0
- package/ios/AudioStreamManager.swift +204 -52
- package/ios/AudioStreamManagerDelegate.swift +4 -0
- package/ios/DataPoint.swift +41 -0
- package/ios/ExpoAudioStreamModule.swift +188 -6
- package/ios/Features.swift +44 -0
- package/ios/RecordingResult.swift +19 -0
- package/ios/RecordingSettings.swift +13 -0
- package/ios/WaveformExtractor.swift +105 -0
- package/package.json +9 -9
- package/plugin/tsconfig.json +13 -8
- package/publish.sh +8 -0
- package/src/AudioRecorder.provider.tsx +1 -1
- package/src/ExpoAudioStream.native.ts +6 -0
- package/src/ExpoAudioStream.types.ts +97 -11
- package/src/ExpoAudioStream.web.ts +228 -0
- package/src/ExpoAudioStreamModule.ts +17 -3
- package/src/WebRecorder.ts +364 -0
- package/src/index.ts +166 -20
- package/src/inlineAudioWebWorker.tsx +340 -0
- package/src/useAudioRecording.tsx +410 -0
- package/src/utils.ts +189 -0
- package/build/ExpoAudioStreamModule.web.d.ts +0 -37
- package/build/ExpoAudioStreamModule.web.d.ts.map +0 -1
- package/build/ExpoAudioStreamModule.web.js +0 -156
- package/build/ExpoAudioStreamModule.web.js.map +0 -1
- package/docs/demo.gif +0 -0
- package/release-it.js +0 -18
- package/src/ExpoAudioStreamModule.web.ts +0 -181
- package/src/useAudioRecording.ts +0 -268
- package/yarn-error.log +0 -7793
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// AudioProcessor.swift
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
import Accelerate
|
|
5
|
+
import AVFoundation
|
|
6
|
+
import QuartzCore
|
|
7
|
+
|
|
8
|
+
public class AudioProcessor {
|
|
9
|
+
public private(set) var audioFile: AVAudioFile?
|
|
10
|
+
private var result: (Any) -> Void
|
|
11
|
+
private var reject: (String, String) -> Void
|
|
12
|
+
private var waveformData = Array<Float>()
|
|
13
|
+
private var progress: Float = 0.0
|
|
14
|
+
private var channelCount: Int = 1
|
|
15
|
+
private var currentProgress: Float = 0.0
|
|
16
|
+
private let extractionQueue = DispatchQueue(label: "AudioProcessor", attributes: .concurrent)
|
|
17
|
+
private var _abortExtraction: Bool = false
|
|
18
|
+
|
|
19
|
+
// Add a counter for unique IDs
|
|
20
|
+
private var uniqueIdCounter = 0
|
|
21
|
+
|
|
22
|
+
public var abortExtraction: Bool {
|
|
23
|
+
get { _abortExtraction }
|
|
24
|
+
set { _abortExtraction = newValue }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Initializer for file-based processing
|
|
28
|
+
public init(url: URL, resolve: @escaping (Any) -> Void, reject: @escaping (String, String) -> Void) throws {
|
|
29
|
+
self.audioFile = try AVAudioFile(forReading: url)
|
|
30
|
+
self.result = resolve
|
|
31
|
+
self.reject = reject
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Initializer for buffer-based processing
|
|
35
|
+
public init(resolve: @escaping (Any) -> Void, reject: @escaping (String, String) -> Void) {
|
|
36
|
+
self.result = resolve
|
|
37
|
+
self.reject = reject
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
deinit {
|
|
42
|
+
audioFile = nil
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Error types for AudioProcessor
|
|
46
|
+
public enum AudioProcessorError: Error {
|
|
47
|
+
case fileInitializationFailed(String)
|
|
48
|
+
case bufferCreationFailed
|
|
49
|
+
case audioReadError(String)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/// Extracts and processes audio data from the audio file.
|
|
54
|
+
/// - Parameters:
|
|
55
|
+
/// - numberOfSamples: The number of samples to extract (for waveform).
|
|
56
|
+
/// - offset: The offset to start reading from (in samples).
|
|
57
|
+
/// - length: The length of the audio to read (in samples).
|
|
58
|
+
/// - pointsPerSecond: The number of data points to extract per second (for features).
|
|
59
|
+
/// - algorithm: The algorithm to use for feature extraction.
|
|
60
|
+
/// - featureOptions: The features to extract.
|
|
61
|
+
/// - bitDepth: The bit depth of the audio data.
|
|
62
|
+
/// - numberOfChannels: The number of channels in the audio data.
|
|
63
|
+
/// - Returns: An `AudioAnalysisData` object containing the extracted features.
|
|
64
|
+
public func processAudioData(numberOfSamples: Int?, offset: Int? = 0, length: UInt? = nil, pointsPerSecond: Int?, algorithm: String, featureOptions: [String: Bool], bitDepth: Int, numberOfChannels: Int) -> AudioAnalysisData? {
|
|
65
|
+
|
|
66
|
+
guard let audioFile = audioFile else {
|
|
67
|
+
reject("FILE_NOT_INITIALIZED", "Audio file is not initialized.")
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let totalFrameCount = AVAudioFrameCount(audioFile.length)
|
|
72
|
+
var framesPerBuffer: AVAudioFrameCount
|
|
73
|
+
let actualPointsPerSecond: Int
|
|
74
|
+
|
|
75
|
+
if let numberOfSamples = numberOfSamples {
|
|
76
|
+
framesPerBuffer = totalFrameCount / AVAudioFrameCount(numberOfSamples)
|
|
77
|
+
actualPointsPerSecond = Int(Double(totalFrameCount) / audioFile.fileFormat.sampleRate)
|
|
78
|
+
} else if let pointsPerSecond = pointsPerSecond {
|
|
79
|
+
actualPointsPerSecond = pointsPerSecond
|
|
80
|
+
framesPerBuffer = totalFrameCount / AVAudioFrameCount(actualPointsPerSecond)
|
|
81
|
+
} else {
|
|
82
|
+
// Default behavior: set pointsPerSecond to 1000
|
|
83
|
+
actualPointsPerSecond = 1000
|
|
84
|
+
framesPerBuffer = totalFrameCount / AVAudioFrameCount(actualPointsPerSecond)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
guard let buffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: framesPerBuffer) else {
|
|
88
|
+
reject("BUFFER_CREATION_FAILED", "Failed to create AVAudioPCMBuffer.")
|
|
89
|
+
return nil
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
channelCount = Int(audioFile.processingFormat.channelCount)
|
|
93
|
+
var data = Array(repeating: [Float](repeating: 0, count: Int(framesPerBuffer)), count: channelCount)
|
|
94
|
+
|
|
95
|
+
var startFrame: AVAudioFramePosition = offset == nil ? audioFile.framePosition : Int64(offset! * Int(framesPerBuffer))
|
|
96
|
+
var endFrame: AVAudioFramePosition = length == nil ? audioFile.length : min(audioFile.length, startFrame + Int64(length!))
|
|
97
|
+
|
|
98
|
+
var channelData = [Float]()
|
|
99
|
+
while startFrame < endFrame {
|
|
100
|
+
if abortExtraction {
|
|
101
|
+
audioFile.framePosition = startFrame
|
|
102
|
+
abortExtraction = false
|
|
103
|
+
return nil
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
do {
|
|
107
|
+
audioFile.framePosition = startFrame
|
|
108
|
+
try audioFile.read(into: buffer, frameCount: framesPerBuffer)
|
|
109
|
+
} catch {
|
|
110
|
+
reject("AUDIO_READ_ERROR", "Couldn't read into buffer: \(error.localizedDescription)")
|
|
111
|
+
return nil
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//TODO: check if we need conversion based on bitDepth here
|
|
115
|
+
guard let floatData = buffer.floatChannelData else {
|
|
116
|
+
reject("BUFFER_DATA_ERROR", "Failed to retrieve float data from buffer.")
|
|
117
|
+
return nil
|
|
118
|
+
}
|
|
119
|
+
for frame in 0..<Int(buffer.frameLength) {
|
|
120
|
+
channelData.append(floatData[0][frame])
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
startFrame += AVAudioFramePosition(framesPerBuffer)
|
|
124
|
+
if startFrame + AVAudioFramePosition(framesPerBuffer) > endFrame {
|
|
125
|
+
framesPerBuffer = AVAudioFrameCount(endFrame - startFrame)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return processChannelData(channelData: channelData, sampleRate: Float(audioFile.fileFormat.sampleRate), pointsPerSecond: actualPointsPerSecond, algorithm: algorithm, featureOptions: featureOptions, bitDepth: bitDepth, numberOfChannels: numberOfChannels)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Processes audio data from a buffer.
|
|
133
|
+
/// - Parameters:
|
|
134
|
+
/// - data: The audio data buffer.
|
|
135
|
+
/// - sampleRate: The sample rate of the audio data.
|
|
136
|
+
/// - pointsPerSecond: The number of data points to extract per second (for features).
|
|
137
|
+
/// - algorithm: The algorithm to use for feature extraction.
|
|
138
|
+
/// - featureOptions: The features to extract.
|
|
139
|
+
/// - bitDepth: The bit depth of the audio data.
|
|
140
|
+
/// - numberOfChannels: The number of channels in the audio data.
|
|
141
|
+
/// - Returns: An `AudioAnalysisData` object containing the extracted features.
|
|
142
|
+
public func processAudioBuffer(data: Data, sampleRate: Float, pointsPerSecond: Int, algorithm: String, featureOptions: [String: Bool], bitDepth: Int, numberOfChannels: Int) -> AudioAnalysisData? {
|
|
143
|
+
guard !data.isEmpty else {
|
|
144
|
+
Logger.debug("Data is empty, rejecting")
|
|
145
|
+
reject("DATA_EMPTY", "The audio data is empty.")
|
|
146
|
+
return nil
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Convert Data to Float array based on bit depth
|
|
150
|
+
let floatData: [Float]
|
|
151
|
+
switch bitDepth {
|
|
152
|
+
case 16:
|
|
153
|
+
floatData = data.withUnsafeBytes { bufferPointer in
|
|
154
|
+
let int16Pointer = bufferPointer.bindMemory(to: Int16.self)
|
|
155
|
+
return int16Pointer.map { Float($0) / Float(Int16.max) }
|
|
156
|
+
}
|
|
157
|
+
case 32:
|
|
158
|
+
floatData = data.withUnsafeBytes { bufferPointer in
|
|
159
|
+
let int32Pointer = bufferPointer.bindMemory(to: Int32.self)
|
|
160
|
+
return int32Pointer.map { Float($0) / Float(Int32.max) }
|
|
161
|
+
}
|
|
162
|
+
default:
|
|
163
|
+
Logger.debug("Unsupported bit depth. Rejecting")
|
|
164
|
+
reject("UNSUPPORTED_BIT_DEPTH", "Unsupported bit depth: \(bitDepth)")
|
|
165
|
+
return nil
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return processChannelData(channelData: floatData, sampleRate: sampleRate, pointsPerSecond: pointsPerSecond, algorithm: algorithm, featureOptions: featureOptions, bitDepth: bitDepth, numberOfChannels: numberOfChannels)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Processes the given audio channel data to extract features.
|
|
172
|
+
/// - Parameters:
|
|
173
|
+
/// - channelData: The audio channel data to process.
|
|
174
|
+
/// - sampleRate: The sample rate of the audio data.
|
|
175
|
+
/// - pointsPerSecond: The number of data points to extract per second (for features).
|
|
176
|
+
/// - algorithm: The algorithm to use for feature extraction.
|
|
177
|
+
/// - featureOptions: The features to extract.
|
|
178
|
+
/// - bitDepth: The bit depth of the audio data.
|
|
179
|
+
/// - numberOfChannels: The number of channels in the audio data.
|
|
180
|
+
/// - Returns: An `AudioAnalysisData` object containing the extracted features.
|
|
181
|
+
private func processChannelData(channelData: [Float], sampleRate: Float, pointsPerSecond: Int, algorithm: String, featureOptions: [String: Bool], bitDepth: Int, numberOfChannels: Int) -> AudioAnalysisData? {
|
|
182
|
+
Logger.debug("Processing audio data with sample rate: \(sampleRate), points per second: \(pointsPerSecond), algorithm: \(algorithm), bitDepth: \(bitDepth), numberOfChannels: \(numberOfChannels)")
|
|
183
|
+
|
|
184
|
+
let startTime = CACurrentMediaTime() // Start the timer with high precision
|
|
185
|
+
|
|
186
|
+
let length = channelData.count
|
|
187
|
+
let pointInterval = Int(sampleRate) / pointsPerSecond
|
|
188
|
+
var dataPoints = [DataPoint]()
|
|
189
|
+
var minAmplitude: Float = .greatestFiniteMagnitude
|
|
190
|
+
var maxAmplitude: Float = -.greatestFiniteMagnitude
|
|
191
|
+
let durationMs = Float(length) / sampleRate * 1000
|
|
192
|
+
|
|
193
|
+
var sumSquares: Float = 0
|
|
194
|
+
var zeroCrossings = 0
|
|
195
|
+
var prevValue: Float = 0
|
|
196
|
+
var localMinAmplitude: Float = .greatestFiniteMagnitude
|
|
197
|
+
var localMaxAmplitude: Float = -.greatestFiniteMagnitude
|
|
198
|
+
var segmentData = [Float]()
|
|
199
|
+
var currentPosition = 0 // Track the current byte position
|
|
200
|
+
|
|
201
|
+
for i in 0..<length {
|
|
202
|
+
updateSegmentData(channelData: channelData, index: i, sumSquares: &sumSquares, zeroCrossings: &zeroCrossings, prevValue: &prevValue, localMinAmplitude: &localMinAmplitude, localMaxAmplitude: &localMaxAmplitude, segmentData: &segmentData)
|
|
203
|
+
|
|
204
|
+
if (i + 1) % pointInterval == 0 || i == length - 1 {
|
|
205
|
+
let features = computeFeatures(segmentData: segmentData, sampleRate: sampleRate, sumSquares: sumSquares, zeroCrossings: zeroCrossings, segmentLength: (i % pointInterval) + 1, featureOptions: featureOptions)
|
|
206
|
+
let rms = features.rms
|
|
207
|
+
let silent = rms < 0.01
|
|
208
|
+
let dB = featureOptions["dB"] == true ? 20 * log10(rms) : 0
|
|
209
|
+
minAmplitude = min(minAmplitude, rms)
|
|
210
|
+
maxAmplitude = max(maxAmplitude, rms)
|
|
211
|
+
|
|
212
|
+
let segmentSize = segmentData.count
|
|
213
|
+
let segmentDuration = Float(segmentSize) / sampleRate
|
|
214
|
+
|
|
215
|
+
// Calculate start time and end time
|
|
216
|
+
let segmentStartTime = Float(i - segmentSize + 1) / sampleRate
|
|
217
|
+
let segmentEndTime = Float(i + 1) / sampleRate
|
|
218
|
+
|
|
219
|
+
// Calculate start position and end position in bytes
|
|
220
|
+
let bytesPerSample = bitDepth / 8
|
|
221
|
+
let startPosition = currentPosition
|
|
222
|
+
let endPosition = startPosition + (segmentSize * bytesPerSample * numberOfChannels)
|
|
223
|
+
|
|
224
|
+
dataPoints.append(DataPoint(
|
|
225
|
+
id: uniqueIdCounter, // Assign unique ID
|
|
226
|
+
amplitude: algorithm == "peak" ? localMaxAmplitude : rms,
|
|
227
|
+
activeSpeech: nil,
|
|
228
|
+
dB: dB,
|
|
229
|
+
silent: silent,
|
|
230
|
+
features: features,
|
|
231
|
+
startTime: segmentStartTime,
|
|
232
|
+
endTime: segmentEndTime,
|
|
233
|
+
startPosition: startPosition,
|
|
234
|
+
endPosition: endPosition,
|
|
235
|
+
speaker: 0
|
|
236
|
+
))
|
|
237
|
+
uniqueIdCounter += 1 // Increment the unique ID counter
|
|
238
|
+
|
|
239
|
+
resetSegmentData(&sumSquares, &zeroCrossings, &localMinAmplitude, &localMaxAmplitude, &segmentData)
|
|
240
|
+
|
|
241
|
+
// Update the current byte position
|
|
242
|
+
currentPosition = endPosition
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let endTime = CACurrentMediaTime() // End the timer with high precision
|
|
247
|
+
let processingTimeMs = Float((endTime - startTime) * 1000)
|
|
248
|
+
|
|
249
|
+
Logger.debug("Processed \(dataPoints.count) data points in \(processingTimeMs) ms")
|
|
250
|
+
|
|
251
|
+
return AudioAnalysisData(
|
|
252
|
+
pointsPerSecond: pointsPerSecond,
|
|
253
|
+
durationMs: durationMs,
|
|
254
|
+
bitDepth: bitDepth,
|
|
255
|
+
numberOfChannels: numberOfChannels,
|
|
256
|
+
sampleRate: sampleRate,
|
|
257
|
+
samples: channelData.count,
|
|
258
|
+
dataPoints: dataPoints,
|
|
259
|
+
amplitudeRange: (min: minAmplitude, max: maxAmplitude),
|
|
260
|
+
speakerChanges: [],
|
|
261
|
+
extractionTimeMs: processingTimeMs
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private func updateSegmentData(channelData: [Float], index: Int, sumSquares: inout Float, zeroCrossings: inout Int, prevValue: inout Float, localMinAmplitude: inout Float, localMaxAmplitude: inout Float, segmentData: inout [Float]) {
|
|
266
|
+
let value = channelData[index]
|
|
267
|
+
sumSquares += value * value
|
|
268
|
+
if index > 0 && value * prevValue < 0 {
|
|
269
|
+
zeroCrossings += 1
|
|
270
|
+
}
|
|
271
|
+
prevValue = value
|
|
272
|
+
|
|
273
|
+
let absValue = abs(value)
|
|
274
|
+
localMinAmplitude = min(localMinAmplitude, absValue)
|
|
275
|
+
localMaxAmplitude = max(localMaxAmplitude, absValue)
|
|
276
|
+
|
|
277
|
+
segmentData.append(value)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private func computeFeatures(segmentData: [Float], sampleRate: Float, sumSquares: Float, zeroCrossings: Int, segmentLength: Int, featureOptions: [String: Bool]) -> Features {
|
|
281
|
+
let rms = sqrt(sumSquares / Float(segmentLength))
|
|
282
|
+
let energy = featureOptions["energy"] == true ? sumSquares : 0
|
|
283
|
+
let zcr = featureOptions["zcr"] == true ? Float(zeroCrossings) / Float(segmentLength) : 0
|
|
284
|
+
let mfcc = featureOptions["mfcc"] == true ? extractMFCC(from: segmentData, sampleRate: sampleRate) : []
|
|
285
|
+
let spectralCentroid = featureOptions["spectralCentroid"] == true ? extractSpectralCentroid(from: segmentData, sampleRate: sampleRate) : 0
|
|
286
|
+
let spectralFlatness = featureOptions["spectralFlatness"] == true ? extractSpectralFlatness(from: segmentData) : 0
|
|
287
|
+
let spectralRollOff = featureOptions["spectralRollOff"] == true ? extractSpectralRollOff(from: segmentData, sampleRate: sampleRate) : 0
|
|
288
|
+
let spectralBandwidth = featureOptions["spectralBandwidth"] == true ? extractSpectralBandwidth(from: segmentData, sampleRate: sampleRate) : 0
|
|
289
|
+
let chromagram = featureOptions["chromagram"] == true ? extractChromagram(from: segmentData, sampleRate: sampleRate) : []
|
|
290
|
+
let tempo = featureOptions["tempo"] == true ? extractTempo(from: segmentData, sampleRate: sampleRate) : 0
|
|
291
|
+
let hnr = featureOptions["hnr"] == true ? extractHNR(from: segmentData) : 0
|
|
292
|
+
|
|
293
|
+
return Features(
|
|
294
|
+
energy: energy,
|
|
295
|
+
mfcc: mfcc,
|
|
296
|
+
rms: rms,
|
|
297
|
+
minAmplitude: 0,
|
|
298
|
+
maxAmplitude: 0,
|
|
299
|
+
zcr: zcr,
|
|
300
|
+
spectralCentroid: spectralCentroid,
|
|
301
|
+
spectralFlatness: spectralFlatness,
|
|
302
|
+
spectralRollOff: spectralRollOff,
|
|
303
|
+
spectralBandwidth: spectralBandwidth,
|
|
304
|
+
chromagram: chromagram,
|
|
305
|
+
tempo: tempo,
|
|
306
|
+
hnr: hnr
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private func resetSegmentData(_ sumSquares: inout Float, _ zeroCrossings: inout Int, _ localMinAmplitude: inout Float, _ localMaxAmplitude: inout Float, _ segmentData: inout [Float]) {
|
|
311
|
+
sumSquares = 0
|
|
312
|
+
zeroCrossings = 0
|
|
313
|
+
localMinAmplitude = .greatestFiniteMagnitude
|
|
314
|
+
localMaxAmplitude = -.greatestFiniteMagnitude
|
|
315
|
+
segmentData.removeAll()
|
|
316
|
+
}
|
|
317
|
+
}
|