Skip to content

Commit

Permalink
ios lint
Browse files Browse the repository at this point in the history
  • Loading branch information
albho committed May 10, 2024
1 parent c1bf63c commit 26e3d53
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 56 deletions.
7 changes: 6 additions & 1 deletion binding/ios/Orca.swift
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,12 @@ public class Orca {
/// - randomState: Random seed for the synthesis process.
/// - Returns: An array of OrcaWord objects representing the word alignments.
/// - Throws: OrcaError
public func synthesizeToFile(text: String, outputURL: URL, speechRate: Double? = nil, randomState: Int64? = nil) throws -> [OrcaWord] {
public func synthesizeToFile(
text: String,
outputURL: URL,
speechRate: Double? = nil,
randomState: Int64? = nil
) throws -> [OrcaWord] {
try synthesizeToFile(text: text, outputPath: outputURL.path, speechRate: speechRate, randomState: randomState)
}

Expand Down
20 changes: 10 additions & 10 deletions binding/ios/OrcaAppTest/OrcaAppTestUITests/BaseTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,14 @@ class BaseTest: XCTestCase {
var orcas: [Orca] = []
var testData: TestData?

let testAudioMaleSingle = Bundle(for: BaseTest.self).url(forResource: "test_resources/wav/orca_params_male_single", withExtension: "wav")!
let testAudioMaleStream = Bundle(for: BaseTest.self).url(forResource: "test_resources/wav/orca_params_male_stream", withExtension: "wav")!
let testAudioFemaleSingle = Bundle(for: BaseTest.self).url(forResource: "test_resources/wav/orca_params_female_single", withExtension: "wav")!
let testAudioFemaleStream = Bundle(for: BaseTest.self).url(forResource: "test_resources/wav/orca_params_female_stream", withExtension: "wav")!
let testAudioMaleSingle = Bundle(for: BaseTest.self)
.url(forResource: "test_resources/wav/orca_params_male_single", withExtension: "wav")!
let testAudioMaleStream = Bundle(for: BaseTest.self)
.url(forResource: "test_resources/wav/orca_params_male_stream", withExtension: "wav")!
let testAudioFemaleSingle = Bundle(for: BaseTest.self)
.url(forResource: "test_resources/wav/orca_params_female_single", withExtension: "wav")!
let testAudioFemaleStream = Bundle(for: BaseTest.self)
.url(forResource: "test_resources/wav/orca_params_female_stream", withExtension: "wav")!

override func setUp() async throws {
try await super.setUp()
Expand Down Expand Up @@ -98,15 +102,12 @@ class BaseTest: XCTestCase {
}

func compareArrays(arr1: [Int16], arr2: [Int16], step: Int) -> Bool {
for i in stride(from: 0, to: arr1.count - step, by: step) {
if !(abs(arr1[i] - arr2[i]) <= 500) {
return false
}
for i in stride(from: 0, to: arr1.count - step, by: step) where !(abs(arr1[i] - arr2[i]) <= 500) {
return false
}
return true
}


func getPcm(fileUrl: URL) throws -> [Int16] {
let data = try Data(contentsOf: fileUrl)
let pcmData = data.withUnsafeBytes { (ptr: UnsafePointer<Int16>) -> [Int16] in
Expand All @@ -116,7 +117,6 @@ class BaseTest: XCTestCase {
return pcmData
}


func validateMetadata(words: [OrcaWord], expectedWords: [OrcaWord], isExpectExact: Bool) {
XCTAssertEqual(words.count, expectedWords.count)

Expand Down
39 changes: 26 additions & 13 deletions binding/ios/OrcaAppTest/OrcaAppTestUITests/OrcaAppTestUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,29 +68,32 @@ class OrcaAppTestUITests: BaseTest {

orcaStream.close()

let groundTruth = try self.getPcm(fileUrl: index == 0 ? self.testAudioMaleStream : self.testAudioFemaleStream)
let groundTruth = try self.getPcm(
fileUrl: index == 0 ? self.testAudioMaleStream : self.testAudioFemaleStream)

XCTAssertEqual(fullPcm.count, groundTruth.count)
XCTAssertTrue(compareArrays(arr1: fullPcm, arr2: groundTruth, step: 1))
}
}


func testSynthesize() throws {
for (index, orca) in self.orcas.enumerated() {
let (pcm, wordArray) = try orca.synthesize(text: self.testData!.test_sentences.text, randomState: self.testData!.random_state)
let (pcm, wordArray) = try orca.synthesize(
text: self.testData!.test_sentences.text, randomState: self.testData!.random_state)
XCTAssertGreaterThan(pcm.count, 0)
XCTAssertGreaterThan(wordArray.count, 0)

let groundTruth = try self.getPcm(fileUrl: index == 0 ? self.testAudioMaleSingle : self.testAudioFemaleSingle)
let groundTruth = try self.getPcm(
fileUrl: index == 0 ? self.testAudioMaleSingle : self.testAudioFemaleSingle)
XCTAssertEqual(pcm.count, groundTruth.count)
XCTAssertTrue(compareArrays(arr1: pcm, arr2: groundTruth, step: 1))
}
}

func testAlignments() throws {
for (index, orca) in self.orcas.enumerated() {
let (pcm, wordArray) = try orca.synthesize(text: self.testData!.test_sentences.text_alignment, randomState: self.testData!.random_state)
let (pcm, wordArray) = try orca.synthesize(
text: self.testData!.test_sentences.text_alignment, randomState: self.testData!.random_state)
XCTAssertGreaterThan(pcm.count, 0)
XCTAssertGreaterThan(wordArray.count, 0)

Expand All @@ -100,17 +103,24 @@ class OrcaAppTestUITests: BaseTest {
appropriateFor: nil,
create: false)
let audioFile = audioDir.appendingPathComponent("test.wav")
let synthToFileWordArray = try orca.synthesizeToFile(text: self.testData!.test_sentences.text_alignment, outputURL: audioFile)
let synthToFileWordArray = try orca.synthesizeToFile(
text: self.testData!.test_sentences.text_alignment, outputURL: audioFile)
try FileManager().removeItem(at: audioFile)

var synthesizeTestData = [OrcaWord]()
for alignment in self.testData!.alignments {
var phonemeArray = [OrcaPhoneme]()
for phoneme in alignment.phonemes {
phonemeArray.append(OrcaPhoneme(phoneme: phoneme.phoneme, startSec: phoneme.start_sec, endSec: phoneme.end_sec))
phonemeArray.append(OrcaPhoneme(
phoneme: phoneme.phoneme, startSec: phoneme.start_sec, endSec: phoneme.end_sec))
}

synthesizeTestData.append(OrcaWord(word: alignment.word, startSec: alignment.start_sec, endSec: alignment.end_sec, phonemeArray: phonemeArray))
synthesizeTestData.append(
OrcaWord(
word: alignment.word,
startSec: alignment.start_sec,
endSec: alignment.end_sec,
phonemeArray: phonemeArray))
}

validateMetadata(words: wordArray, expectedWords: synthesizeTestData, isExpectExact: index == 1)
Expand All @@ -128,8 +138,10 @@ class OrcaAppTestUITests: BaseTest {

func testSynthesizeSpeechRate() throws {
for orca in self.orcas {
let (pcm: pcmFast, wordArray: wordArrayFast) = try orca.synthesize(text: self.testData!.test_sentences.text, speechRate: 1.3)
let (pcm: pcmSlow, wordArray: wordArraySlow) = try orca.synthesize(text: self.testData!.test_sentences.text, speechRate: 0.7)
let (pcm: pcmFast, wordArray: wordArrayFast) = try orca.synthesize(
text: self.testData!.test_sentences.text, speechRate: 1.3)
let (pcm: pcmSlow, wordArray: wordArraySlow) = try orca.synthesize(
text: self.testData!.test_sentences.text, speechRate: 0.7)

XCTAssertLessThan(pcmFast.count, pcmSlow.count)
XCTAssertEqual(wordArrayFast.count, wordArraySlow.count)
Expand All @@ -144,19 +156,20 @@ class OrcaAppTestUITests: BaseTest {

func testSynthesizeRandomState() throws {
for orca in self.orcas {
let (pcm: pcm1, wordArray: wordArray1) = try orca.synthesize(text: self.testData!.test_sentences.text, randomState: 1)
let (pcm: pcm1, wordArray: wordArray1) = try orca.synthesize(
text: self.testData!.test_sentences.text, randomState: 1)
XCTAssertGreaterThan(pcm1.count, 0)
XCTAssertGreaterThan(wordArray1.count, 0)

let (pcm: pcm2, wordArray: wordArray2) = try orca.synthesize(text: self.testData!.test_sentences.text, randomState: 2)
let (pcm: pcm2, wordArray: wordArray2) = try orca.synthesize(
text: self.testData!.test_sentences.text, randomState: 2)
XCTAssertGreaterThan(pcm2.count, 0)
XCTAssertGreaterThan(wordArray2.count, 0)

XCTAssertNotEqual(pcm1, pcm2)
}
}


func testSynthesizeToFile() throws {
let audioDir = try FileManager.default.url(
for: .documentDirectory,
Expand Down
6 changes: 3 additions & 3 deletions binding/ios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Made in Vancouver, Canada by [Picovoice](https://picovoice.ai)

Orca is an on-device streaming text-to-speech engine that is designed for use with LLMs, enabling zero-latency
voice assistants. Orca is:
Orca is an on-device streaming text-to-speech engine that is designed for use with LLMs, enabling zero-latency voice
assistants. Orca is:

- Private; All voice processing runs locally.
- Cross-Platform:
Expand Down Expand Up @@ -146,7 +146,7 @@ and replace `${MODEL_FILE_PATH}` or `${MODEL_FILE_URL}` with the path to the mod

### Speech control

Orca allows for keyword arguments to control the synthesized speech. They can be provided to the `streamOopen`
Orca allows for keyword arguments to control the synthesized speech. They can be provided to the `streamOpen`
method or the single synthesis methods `synthesize` and `synthesizeToFile`:

- `speechRate`: Controls the speed of the generated speech. Valid values are within [0.7, 1.3]. A higher (lower) value
Expand Down
6 changes: 3 additions & 3 deletions demo/ios/OrcaDemo/OrcaDemo/AtomicBool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import Foundation
class AtomicBool {
private var value: Bool
private let lock = NSLock()

init(_ value: Bool = false) {
self.value = value
}

func set(_ newValue: Bool) {
lock.lock()
value = newValue
lock.unlock()
}

func get() -> Bool {
lock.lock()
defer { lock.unlock() }
Expand Down
29 changes: 17 additions & 12 deletions demo/ios/OrcaDemo/OrcaDemo/AudioPlayerStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,30 @@ class AudioPlayerStream {
private let engine = AVAudioEngine()
private let playerNode = AVAudioPlayerNode()
private let mixerNode = AVAudioMixerNode()

private var pcmBuffers = [[Int16]]()
private var isPlaying = false

init(sampleRate: Double) throws {
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback, mode: .default)
try audioSession.setActive(true)

let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: sampleRate, channels: AVAudioChannelCount(1), interleaved: false)


let format = AVAudioFormat(
commonFormat: .pcmFormatFloat32,
sampleRate: sampleRate,
channels: AVAudioChannelCount(1),
interleaved: false)

engine.attach(mixerNode)
engine.connect(mixerNode, to: engine.outputNode, format: format)

engine.attach(playerNode)
engine.connect(playerNode, to: mixerNode, format: format)

try engine.start()
}

func playStreamPCM(_ pcmData: [Int16], completion: @escaping (Bool) -> Void) {
pcmBuffers.append(pcmData)
if !isPlaying {
Expand All @@ -33,17 +37,18 @@ class AudioPlayerStream {
completion(true)
}
}

private func playNextPCMBuffer(completion: @escaping (Bool) -> Void) {
guard let pcmData = pcmBuffers.first, !pcmData.isEmpty else {
isPlaying = false
completion(false)
return
}
pcmBuffers.removeFirst()

let audioBuffer = AVAudioPCMBuffer(pcmFormat: playerNode.outputFormat(forBus: 0), frameCapacity: AVAudioFrameCount(pcmData.count))!


let audioBuffer = AVAudioPCMBuffer(
pcmFormat: playerNode.outputFormat(forBus: 0), frameCapacity: AVAudioFrameCount(pcmData.count))!

audioBuffer.frameLength = audioBuffer.frameCapacity
let buf = audioBuffer.floatChannelData![0]
for (index, sample) in pcmData.enumerated() {
Expand Down
9 changes: 5 additions & 4 deletions demo/ios/OrcaDemo/OrcaDemo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct ContentView: View {
|| viewModel.state == .INIT || (!streamingMode && !viewModel.invalidTextMessage.isEmpty)
let toggleDisabled = interactionDisabled || viewModel.state == .STREAM_PLAYING
let buttonDisabled = toggleDisabled || text.isEmpty

GeometryReader { _ in
VStack(spacing: 10) {
Toggle(
Expand All @@ -38,7 +38,7 @@ struct ContentView: View {
.disabled(toggleDisabled)
.onChange(of: streamingMode) { _ in text = "" }
.foregroundColor(Color.black)

if viewModel.state == .STREAM_PLAYING {
GeometryReader { geometry in
ScrollView {
Expand Down Expand Up @@ -71,7 +71,8 @@ struct ContentView: View {
.background(lightGray)
.foregroundColor(Color.black)
.onChange(of: text) { newValue in
let updatedText = String(newValue.prefix(Int(exactly: viewModel.maxCharacterLimit)!))
let updatedText = String(
newValue.prefix(Int(exactly: viewModel.maxCharacterLimit)!))
text = updatedText.replacingOccurrences(of: "", with: "'")
viewModel.isValid(text: text)
}
Expand All @@ -93,7 +94,7 @@ struct ContentView: View {
}
}
}

if streamingMode {
if viewModel.state == .STREAM_OPEN && !viewModel.streamInvalidTextMessage.isEmpty {
Text(viewModel.streamInvalidTextMessage)
Expand Down
16 changes: 8 additions & 8 deletions demo/ios/OrcaDemo/OrcaDemo/ViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum UIState {

class ViewModel: ObservableObject {
private let ACCESS_KEY = "{YOUR_ACCESS_KEY_HERE}" // Obtained from Picovoice Console (https://console.picovoice.ai)

private let NUM_AUDIO_WAIT_CHUNKS = 1

private var orca: Orca!
Expand All @@ -36,7 +36,7 @@ class ViewModel: ObservableObject {

private let audioFilePath = "temp.wav"
private var audioFile: URL!

@Published var state = UIState.INIT
@Published var sampleRate: Int32 = 0
@Published var maxCharacterLimit: Int32 = 0
Expand All @@ -45,7 +45,7 @@ class ViewModel: ObservableObject {
@Published var errorMessage = ""
@Published var invalidTextMessage = ""
@Published var streamInvalidTextMessage = ""

init() {
initialize()
}
Expand Down Expand Up @@ -110,18 +110,18 @@ class ViewModel: ObservableObject {
runStreamSynthesis(text: text)
return
}

if state == UIState.PLAYING {
toggleSynthesizeOff()
} else {
toggleSynthesizeOn(text: text)
}
}

private func runStreamSynthesis(text: String) {
self.textStream = ""
self.state = UIState.STREAM_PLAYING

do {
playerStream = try AudioPlayerStream(sampleRate: Double(self.sampleRate))
} catch {
Expand Down Expand Up @@ -186,7 +186,7 @@ class ViewModel: ObservableObject {
let playStreamQueue = DispatchQueue(label: "play-stream-queue")
let pcmStreamQueueLatch = DispatchSemaphore(value: 0)
let playStreamQueueLatch = DispatchSemaphore(value: 0)

func getSecsString(secs: Float) -> String {
return "Seconds of audio synthesized: " + String(format: "%.3f", secs) + "s"
}
Expand Down Expand Up @@ -292,7 +292,7 @@ class ViewModel: ObservableObject {
}
}
}

public func toggleSynthesizeOff() {
player.stop()
state = UIState.READY
Expand Down
3 changes: 2 additions & 1 deletion resources/.lint/spell-check/dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ itok
numpy
btns
Btns
pltf
pltf
usleep
4 changes: 3 additions & 1 deletion resources/.lint/swift/.swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ disabled_rules:
- implicit_getter
- cyclomatic_complexity
- function_parameter_count
- file_length
- type_body_length
excluded:
- ${PWD}/**/Pods
- ${PWD}/**/Pods

0 comments on commit 26e3d53

Please sign in to comment.