diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5b1982f --- /dev/null +++ b/.clang-format @@ -0,0 +1,88 @@ +# Generated from CLion C/C++ Code Style settings +BasedOnStyle: LLVM +Cpp11BracedListStyle: true +AccessModifierOffset: -4 +AlignConsecutiveMacros: true +AlignTrailingComments: false +AlignAfterOpenBracket: Align +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AlignArrayOfStructures: Left +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: None +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Custom +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterExternBlock: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true +# BraceBreakingStyle: Attach +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +ColumnLimit: 0 +CommentPragmas: '^[^ ]' +CompactNamespaces: false +ContinuationIndentWidth: 8 +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +InsertTrailingCommas: Wrapped +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Right +ReflowComments: false +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never \ No newline at end of file diff --git a/.github/workflows/c-codestyle.yml b/.github/workflows/c-codestyle.yml new file mode 100644 index 0000000..08ce31e --- /dev/null +++ b/.github/workflows/c-codestyle.yml @@ -0,0 +1,32 @@ +name: C Codestyle + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - '**/*.c' + - '.github/workflows/c-codestyle.yml' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+' ] + paths: + - '**/*.c' + - '.github/workflows/c-codestyle.yml' + +jobs: + check-c-codestyle: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: sudo apt install clang-format + + - name: Check c codestyle + run: python3 res/.lint/c/formatter.py -c -v \ No newline at end of file diff --git a/.github/workflows/ios-demos.yml b/.github/workflows/ios-demos.yml new file mode 100644 index 0000000..4bc07f8 --- /dev/null +++ b/.github/workflows/ios-demos.yml @@ -0,0 +1,52 @@ +name: iOS Demos + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - 'demo/ios/OctopusDemo/**' + - '.github/workflows/ios-demos.yml' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+' ] + paths: + - 'demo/ios/OctopusDemo/**' + - '.github/workflows/ios-demos.yml' + +defaults: + run: + working-directory: demo/ios/OctopusDemo + +jobs: + build: + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Node.js LTS + uses: actions/setup-node@v3 + with: + node-version: lts/* + + - name: Install Cocoapods + run: gem install cocoapods + + - name: Install AppCenter CLI + run: npm install -g appcenter-cli + + - name: Make build dir + run: mkdir ddp + + - name: Run Cocoapods + run: pod install + + - name: Build + run: xcrun xcodebuild build + -configuration Debug + -workspace OctopusDemo.xcworkspace + -sdk iphoneos + -scheme OctopusDemo + -derivedDataPath ddp + CODE_SIGNING_ALLOWED=NO \ No newline at end of file diff --git a/.github/workflows/java-codestyle.yml b/.github/workflows/java-codestyle.yml new file mode 100644 index 0000000..4e827d6 --- /dev/null +++ b/.github/workflows/java-codestyle.yml @@ -0,0 +1,28 @@ +name: Java CodeStyle + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - '**/*.java' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+' ] + paths: + - '**/*.java' + +jobs: + check-java-codestyle: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + + - name: Check Java CodeStyle + run: java -Dconfig_loc=res/.lint/java/ -jar res/.lint/java/checkstyle-10.5.0-all.jar -c res/.lint/java/checkstyle.xml binding/android/ binding/java/ binding/flutter/android/ binding/react-native/android/ demo/android/ demo/java/ \ No newline at end of file diff --git a/.github/workflows/python-codestyle.yml b/.github/workflows/python-codestyle.yml new file mode 100644 index 0000000..b6f577f --- /dev/null +++ b/.github/workflows/python-codestyle.yml @@ -0,0 +1,32 @@ +name: Python Codestyle + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - 'binding/python/*.py' + - 'demo/python/*.py' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+' ] + paths: + - 'binding/python/*.py' + - 'demo/python/*.py' + +jobs: + check-python-codestyle: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: pip install flake8 pep8-naming + + - name: Check python codestyle + run: flake8 --ignore=F401,F403,F405 --max-line-length=120 binding/python demo/python \ No newline at end of file diff --git a/.github/workflows/python-demos.yml b/.github/workflows/python-demos.yml new file mode 100644 index 0000000..af98c18 --- /dev/null +++ b/.github/workflows/python-demos.yml @@ -0,0 +1,46 @@ +name: Python Demos + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - '.github/workflows/python-demos.yml' + - 'demo/python/**' + - '!demo/python/README.md' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+' ] + paths: + - '.github/workflows/python-demos.yml' + - 'demo/python/**' + - '!demo/python/README.md' + +defaults: + run: + working-directory: demo/python + +jobs: + build-github-hosted: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ['3.7', '3.8', '3.9', '3.10'] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Pre-build dependencies + run: python -m pip install --upgrade pip + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Test + run: python octopus_demo.py --access_key ${{secrets.PV_VALID_ACCESS_KEY}} --audio_paths ../../res/audio/multiple_keywords.wav --search_phrase porcupine diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 27fb42a..5aef06c 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -22,4 +22,4 @@ jobs: run: npm install -g cspell - name: Run CSpell - run: cspell --config res/spell-check/.cspell.json "**/*" \ No newline at end of file + run: cspell --config res/.lint/spell-check/.cspell.json "**/*" \ No newline at end of file diff --git a/.github/workflows/swift-codestyle.yml b/.github/workflows/swift-codestyle.yml new file mode 100644 index 0000000..4a106d7 --- /dev/null +++ b/.github/workflows/swift-codestyle.yml @@ -0,0 +1,24 @@ +name: Swift Codestyle + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - '**/*.swift' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+' ] + paths: + - '**/*.swift' + +jobs: + check-switch-codestyle: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Check swift codestyle + uses: norio-nomura/action-swiftlint@3.2.1 + with: + args: lint --config res/.lint/swift/.swiftlint.yml --strict \ No newline at end of file diff --git a/.github/workflows/web-codestyle.yml b/.github/workflows/web-codestyle.yml new file mode 100644 index 0000000..df5e018 --- /dev/null +++ b/.github/workflows/web-codestyle.yml @@ -0,0 +1,35 @@ +name: Web Codestyle + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - '**/web/*.js' + - '**/web/*.ts' + - '.github/workflows/web-codestyle.yml' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+' ] + paths: + - '**/web/*.js' + - '**/web/*.ts' + - '.github/workflows/web-codestyle.yml' + +jobs: + check-web-codestyle: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Node.js LTS + uses: actions/setup-node@v3 + with: + node-version: lts/* + + - name: Pre-build dependencies + run: npm install yarn + + - name: Run Binding Linter + run: yarn && yarn lint + working-directory: binding/web diff --git a/.github/workflows/web-demos.yml b/.github/workflows/web-demos.yml new file mode 100644 index 0000000..bacfb52 --- /dev/null +++ b/.github/workflows/web-demos.yml @@ -0,0 +1,42 @@ +name: Web Demos + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - 'demo/web/**' + - '!demo/web/README.md' + - '.github/workflows/web-demos.yml' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+' ] + paths: + - 'demo/web/**' + - '!demo/web/README.md' + - '.github/workflows/web-demos.yml' + +defaults: + run: + working-directory: demo/web + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x, 16.x, 18.x, 20.x] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Pre-build dependencies + run: npm install yarn + + - name: Install dependencies + run: yarn install diff --git a/binding/android/octopus/src/main/java/ai/picovoice/octopus/Octopus.java b/binding/android/octopus/src/main/java/ai/picovoice/octopus/Octopus.java index b69f2e6..f24ada6 100644 --- a/binding/android/octopus/src/main/java/ai/picovoice/octopus/Octopus.java +++ b/binding/android/octopus/src/main/java/ai/picovoice/octopus/Octopus.java @@ -60,7 +60,11 @@ private static void extractPackageResources(Context context) throws OctopusExcep } } - private static String extractResource(Context context, InputStream srcFileStream, String dstFilename) throws IOException { + private static String extractResource( + Context context, + InputStream srcFileStream, + String dstFilename + ) throws IOException { InputStream is = new BufferedInputStream(srcFileStream, 256); OutputStream os = new BufferedOutputStream(context.openFileOutput(dstFilename, Context.MODE_PRIVATE), 256); int r; @@ -185,6 +189,9 @@ public String getVersion() { return OctopusNative.getVersion(); } + /** + * Builder for creating an instance of Octopus with a mixture of default arguments. + */ public static class Builder { private String accessKey = null; @@ -200,6 +207,9 @@ public Builder setModelPath(String modelPath) { return this; } + /** + * Creates an instance of Octopus Speech-to-Index engine. + */ public Octopus build(Context context) throws OctopusException { if (accessKey == null || this.accessKey.equals("")) { throw new OctopusInvalidArgumentException("No AccessKey was provided to Octopus"); diff --git a/binding/android/octopus/src/main/java/ai/picovoice/octopus/OctopusMatch.java b/binding/android/octopus/src/main/java/ai/picovoice/octopus/OctopusMatch.java index ca3758a..467f002 100644 --- a/binding/android/octopus/src/main/java/ai/picovoice/octopus/OctopusMatch.java +++ b/binding/android/octopus/src/main/java/ai/picovoice/octopus/OctopusMatch.java @@ -10,12 +10,22 @@ package ai.picovoice.octopus; +/** + * Class that contains match results returned from Octopus search. + */ public class OctopusMatch { private final float startSec; private final float endSec; private final float probability; + /** + * Constructor. + * + * @param startSec The starting second of the match word. + * @param endSec The ending second of the match word. + * @param probability Floating-point value between [0, 1]. + */ public OctopusMatch(float startSec, float endSec, float probability) { this.startSec = startSec; this.endSec = endSec; diff --git a/binding/android/octopus/src/main/java/ai/picovoice/octopus/OctopusMetadata.java b/binding/android/octopus/src/main/java/ai/picovoice/octopus/OctopusMetadata.java index c297ab4..615abd3 100644 --- a/binding/android/octopus/src/main/java/ai/picovoice/octopus/OctopusMetadata.java +++ b/binding/android/octopus/src/main/java/ai/picovoice/octopus/OctopusMetadata.java @@ -10,6 +10,9 @@ package ai.picovoice.octopus; +/** + * Class that contains metadata returned from Octopus index. + */ public class OctopusMetadata { static { @@ -32,11 +35,11 @@ public OctopusMetadata(byte[] metadataBytes) { } /** - * Gets the metadata in the form of a byte array + * Gets the metadata in the form of a byte array. * * @return the metadata in the form of a byte array. */ - public byte[] getBytes(){ + public byte[] getBytes() { return metadataNative.getBytes(); } diff --git a/binding/ios/Octopus.swift b/binding/ios/Octopus.swift index 5d09bad..8f67962 100644 --- a/binding/ios/Octopus.swift +++ b/binding/ios/Octopus.swift @@ -14,7 +14,7 @@ public struct OctopusMatch { public let startSec: Float public let endSec: Float public let probability: Float - + public init(startSec: Float, endSec: Float, probability: Float) { self.startSec = startSec self.endSec = endSec @@ -24,59 +24,59 @@ public struct OctopusMatch { /// iOS binding for Octopus Speech-to-Index engine. It transforms audio into searchable metadata. public class Octopus { - + /// Required audio sample rate for PCM data public static let pcmDataSampleRate = Int(pv_sample_rate()) - + /// Octopus version public static let version = String(cString: pv_octopus_version()) - + private var handle: OpaquePointer? - + /// Constructor. /// /// - Parameters: /// - accessKey: AccessKey obtained from the Picovoice Console (https://console.picovoice.ai/) /// - modelPath: Absolute path to file containing model parameters. /// - Throws: OctopusError - public init(accessKey:String, modelPath:String? = nil) throws { - + public init(accessKey: String, modelPath: String? = nil) throws { + var modelPathArg = modelPath - - if (modelPath == nil) { + + if modelPath == nil { let bundle = Bundle(for: type(of: self)) - + modelPathArg = bundle.path(forResource: "octopus_params", ofType: "pv") if modelPathArg == nil { throw OctopusIOError("Could not retrieve default model from app bundle") } } - + if !FileManager().fileExists(atPath: modelPathArg!) { modelPathArg = try getResourcePath(modelPathArg!) } - + let status = pv_octopus_init( accessKey, modelPathArg, &handle) - if(status != PV_STATUS_SUCCESS) { + if status != PV_STATUS_SUCCESS { throw pvStatusToOctopusError(status, "Octopus init failed") } } - + deinit { self.delete() } - + /// Releases resources acquired by Octopus. public func delete() { if handle != nil { - pv_octopus_delete(handle); + pv_octopus_delete(handle) handle = nil } } - + /// Indexes raw PCM data. /// /// - Parameters: @@ -84,104 +84,105 @@ public class Octopus { /// equal to `.pcmDataSampleRate` and be single-channel, 16-bit linearly-encoded. /// - Throws: OctopusError /// - Returns: OctopusMetadata object that is used to perform searches - public func indexAudioData(pcm:[Int16]) throws -> OctopusMetadata { - + public func indexAudioData(pcm: [Int16]) throws -> OctopusMetadata { + if handle == nil { throw OctopusInvalidStateError("Octopus must be initialized before indexing") } - + var cMetadata: UnsafeMutableRawPointer? - var cNumMetadataBytes : Int32 = -1 + var cNumMetadataBytes: Int32 = -1 let status = pv_octopus_index( handle, pcm, Int32(pcm.count), &cMetadata, &cNumMetadataBytes) - - if(status != PV_STATUS_SUCCESS) { + + if status != PV_STATUS_SUCCESS { throw pvStatusToOctopusError(status, "Octopus index failed") } - + let numMetadataBytes = Int(cNumMetadataBytes) let metadata = cMetadata!.bindMemory(to: UInt8.self, capacity: numMetadataBytes) - + return OctopusMetadata(handle: metadata, numBytes: numMetadataBytes) } - + /// Reads and indexes a given audio file. /// /// - Parameters: /// - path: Absolute path to an audio file. /// - Throws: OctopusError /// - Returns: OctopusMetadata object that is used to perform searches - public func indexAudioFile(path:String) throws -> OctopusMetadata { + public func indexAudioFile(path: String) throws -> OctopusMetadata { if handle == nil { throw OctopusInvalidStateError("Octopus must be initialized before indexing") } var pathArg = path - if !FileManager().fileExists(atPath: pathArg){ + if !FileManager().fileExists(atPath: pathArg) { pathArg = try getResourcePath(pathArg) } - + var cMetadata: UnsafeMutableRawPointer? - var cNumMetadataBytes : Int32 = -1 + var cNumMetadataBytes: Int32 = -1 let status = pv_octopus_index_file( handle, pathArg, &cMetadata, &cNumMetadataBytes) - - if(status != PV_STATUS_SUCCESS) { + + if status != PV_STATUS_SUCCESS { throw pvStatusToOctopusError(status, "Octopus index failed") } - + let numMetadataBytes = Int(cNumMetadataBytes) let metadata = cMetadata!.bindMemory(to: UInt8.self, capacity: numMetadataBytes) - + return OctopusMetadata(handle: metadata, numBytes: numMetadataBytes) } - + /// Reads and indexes a given audio file. /// /// - Parameters: /// - metadata: An `OctopusMetadata` object obtained via `.indexAudioData` or `.indexAudioFile` /// - phrases: A set of phrases to search for in the metadata /// - Throws: OctopusError - /// - Returns: A dictionary of phrases and match arrays. Matches are represented by immutable `OctopusMatch` objects. - public func search(metadata: OctopusMetadata, phrases:Set) throws -> Dictionary { + /// - Returns: A dictionary of phrases and match arrays. + /// Matches are represented by immutable `OctopusMatch` objects. + public func search(metadata: OctopusMetadata, phrases: Set) throws -> [String: [OctopusMatch]] { if handle == nil { throw OctopusInvalidStateError("Octopus must be initialized before searching") } - - var matches = Dictionary() - + + var matches = [String: [OctopusMatch]]() + for phrase in phrases { - + let formattedPhrase: String = phrase.trimmingCharacters(in: .whitespacesAndNewlines) .replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression) .replacingOccurrences(of: "[‘’]+", with: "'", options: .regularExpression) .split(separator: " ") .joined(separator: " ") - + if formattedPhrase.isEmpty { throw OctopusInvalidArgumentError("Search phrase cannot be empty") } - - var cMatches:UnsafeMutablePointer? - var cNumMatches:Int32 = -1 - + + var cMatches: UnsafeMutablePointer? + var cNumMatches: Int32 = -1 + let status = pv_octopus_search(handle, metadata.handle, Int32(metadata.numBytes), formattedPhrase, &cMatches, &cNumMatches) - if(status != PV_STATUS_SUCCESS) { + if status != PV_STATUS_SUCCESS { throw pvStatusToOctopusError(status, "Octopus search failed") } - + let numPhraseMatches = Int(cNumMatches) var phraseMatches = [OctopusMatch]() for cMatch in UnsafeBufferPointer(start: cMatches, count: numPhraseMatches) { @@ -191,10 +192,10 @@ public class Octopus { probability: Float(cMatch.probability)) phraseMatches.append(phraseMatch) } - + matches[formattedPhrase] = phraseMatches } - + return matches } @@ -206,14 +207,16 @@ public class Octopus { /// - Returns: The full path of the resource. private func getResourcePath(_ filePath: String) throws -> String { if let resourcePath = Bundle(for: type(of: self)).resourceURL?.appendingPathComponent(filePath).path { - if (FileManager.default.fileExists(atPath: resourcePath)) { + if FileManager.default.fileExists(atPath: resourcePath) { return resourcePath } } - throw OctopusIOError("Could not find file at path '\(filePath)'. If this is a packaged asset, ensure you have added it to your xcode project.") + throw OctopusIOError( + "Could not find file at path '\(filePath)'." + + "If this is a packaged asset, ensure you have added it to your xcode project.") } - + private func pvStatusToOctopusError(_ status: pv_status_t, _ message: String) -> OctopusError { switch status { case PV_STATUS_OUT_OF_MEMORY: diff --git a/binding/ios/OctopusAppTest/OctopusAppTest/AppDelegate.swift b/binding/ios/OctopusAppTest/OctopusAppTest/AppDelegate.swift index aa4a5fe..ed7cfae 100644 --- a/binding/ios/OctopusAppTest/OctopusAppTest/AppDelegate.swift +++ b/binding/ios/OctopusAppTest/OctopusAppTest/AppDelegate.swift @@ -13,7 +13,10 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { return true } } diff --git a/binding/ios/OctopusAppTest/OctopusAppTest/ViewController.swift b/binding/ios/OctopusAppTest/OctopusAppTest/ViewController.swift index bed17e3..8e61564 100644 --- a/binding/ios/OctopusAppTest/OctopusAppTest/ViewController.swift +++ b/binding/ios/OctopusAppTest/OctopusAppTest/ViewController.swift @@ -15,6 +15,4 @@ class ViewController: UIViewController { super.viewDidLoad() } - } - diff --git a/binding/ios/OctopusAppTest/OctopusAppTestUITests/OctopusAppTestUITests.swift b/binding/ios/OctopusAppTest/OctopusAppTestUITests/OctopusAppTestUITests.swift index 3207c50..a73f7bf 100644 --- a/binding/ios/OctopusAppTest/OctopusAppTestUITests/OctopusAppTestUITests.swift +++ b/binding/ios/OctopusAppTest/OctopusAppTestUITests/OctopusAppTestUITests.swift @@ -21,197 +21,197 @@ class OctopusAppTestUITests: BaseTest { func testIndexAndSearchFile() throws { let bundle = Bundle(for: type(of: self)) let audioFilePath = bundle.path(forResource: "multiple_keywords", ofType: "wav")! - + let octopus = try Octopus(accessKey: accessKey) let metadata = try octopus.indexAudioFile(path: audioFilePath) let matches = try octopus.search(metadata: metadata, phrases: phrases) XCTAssert(matches["gorilla"]!.count == 0) XCTAssert(matches["terminator"]!.count == 1) - + let terminatorMatches = matches["terminator"]! XCTAssertEqual(terminatorMatches[0].startSec, expectedMatch.startSec, accuracy: 0.01) XCTAssertEqual(terminatorMatches[0].endSec, expectedMatch.endSec, accuracy: 0.01) XCTAssertEqual(terminatorMatches[0].probability, expectedMatch.probability, accuracy: 0.01) - + metadata.delete() octopus.delete() } func testIndexAndSearchData() throws { let bundle = Bundle(for: type(of: self)) - let fileURL:URL = bundle.url(forResource: "multiple_keywords", withExtension: "wav")! + let fileURL: URL = bundle.url(forResource: "multiple_keywords", withExtension: "wav")! let audioData = try Data(contentsOf: fileURL) - var pcm = Array(repeating: 0, count: (audioData.count - 44) / 2) + var pcm = [Int16](repeating: 0, count: (audioData.count - 44) / 2) _ = pcm.withUnsafeMutableBytes { audioData.copyBytes(to: $0, from: 44.. = [""] var invalidArg = false do { - let _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) + _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) } catch is OctopusInvalidArgumentError { invalidArg = true } - + XCTAssert(invalidArg) - + metadata.delete() octopus.delete() } - + func testWhitespaceSearchPhrase() throws { let bundle = Bundle(for: type(of: self)) let audioFilePath = bundle.path(forResource: "multiple_keywords", ofType: "wav")! - + let octopus = try Octopus(accessKey: accessKey) let metadata = try octopus.indexAudioFile(path: audioFilePath) - + let invalidPhrase: Set = [" "] var invalidArg = false do { - let _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) + _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) } catch is OctopusInvalidArgumentError { invalidArg = true } - + XCTAssert(invalidArg) - + metadata.delete() octopus.delete() } - + func testNumericSearchPhrase() throws { let bundle = Bundle(for: type(of: self)) let audioFilePath = bundle.path(forResource: "multiple_keywords", ofType: "wav")! - + let octopus = try Octopus(accessKey: accessKey) let metadata = try octopus.indexAudioFile(path: audioFilePath) - + let invalidPhrase: Set = ["12"] var invalidArg = false do { - let _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) + _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) } catch is OctopusInvalidArgumentError { invalidArg = true } - + XCTAssert(invalidArg) - + metadata.delete() octopus.delete() } - + func testHyphenInSearchPhrase() throws { let bundle = Bundle(for: type(of: self)) let audioFilePath = bundle.path(forResource: "multiple_keywords", ofType: "wav")! - + let octopus = try Octopus(accessKey: accessKey) let metadata = try octopus.indexAudioFile(path: audioFilePath) - + let invalidPhrase: Set = ["real-time"] var invalidArg = false do { - let _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) + _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) } catch is OctopusInvalidArgumentError { invalidArg = true } - + XCTAssert(invalidArg) - + metadata.delete() octopus.delete() } - + func testInvalidSearchPhrase() throws { let bundle = Bundle(for: type(of: self)) let audioFilePath = bundle.path(forResource: "multiple_keywords", ofType: "wav")! - + let octopus = try Octopus(accessKey: accessKey) let metadata = try octopus.indexAudioFile(path: audioFilePath) - + let invalidPhrase: Set = ["@@!%$"] var invalidArg = false do { - let _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) + _ = try octopus.search(metadata: metadata, phrases: invalidPhrase) } catch is OctopusInvalidArgumentError { invalidArg = true } - + XCTAssert(invalidArg) - + metadata.delete() octopus.delete() } - + func testSpacesInSearchPhrase() throws { let bundle = Bundle(for: type(of: self)) let audioFilePath = bundle.path(forResource: "multiple_keywords", ofType: "wav")! - + let octopus = try Octopus(accessKey: accessKey) let metadata = try octopus.indexAudioFile(path: audioFilePath) - + let searchPhrase: Set = [" americano avocado "] let normalizedSearchPhrase = "americano avocado" - + let matches = try octopus.search(metadata: metadata, phrases: searchPhrase) XCTAssert(matches[normalizedSearchPhrase]!.count == 1) - + let match = matches[normalizedSearchPhrase]![0] let expected = OctopusMatch(startSec: 9.47, endSec: 12.25, probability: 0.43) XCTAssertEqual(match.startSec, expected.startSec, accuracy: 0.01) XCTAssertEqual(match.endSec, expected.endSec, accuracy: 0.01) XCTAssertEqual(match.probability, expected.probability, accuracy: 0.01) - + metadata.delete() octopus.delete() } diff --git a/binding/ios/OctopusAppTest/OctopusAppTestUITests/OctopusLanguageTests.swift b/binding/ios/OctopusAppTest/OctopusAppTestUITests/OctopusLanguageTests.swift index ebfa6f2..474a018 100644 --- a/binding/ios/OctopusAppTest/OctopusAppTestUITests/OctopusLanguageTests.swift +++ b/binding/ios/OctopusAppTest/OctopusAppTestUITests/OctopusLanguageTests.swift @@ -25,14 +25,14 @@ class OctopusLanguageTests: BaseTest { ["it", ["porcospino": [[0.480, 1.728, 1]]]], ["ja", ["りんご": [[0.960, 1.664, 1]]]], ["ko", ["아이스크림": [[6.592, 7.520, 0.961]]]], - ["pt", ["porco espinho": [[0.480, 1.792, 1]]]], + ["pt", ["porco espinho": [[0.480, 1.792, 1]]]] ] var language: String = "" var modelPath: String = "" var keywordPaths: [String] = [] var testAudioPath: String = "" - var expectedResults: [String:[[Double]]] = [:] + var expectedResults: [String: [[Double]]] = [:] override class var defaultTestSuite: XCTestSuite { get { @@ -47,7 +47,7 @@ class OctopusLanguageTests: BaseTest { newTestCase.language = testCase[0] as! String newTestCase.modelPath = bundle.path(forResource: "octopus_params\(suffix)", ofType: "pv")! newTestCase.testAudioPath = bundle.path(forResource: "multiple_keywords\(suffix)", ofType: "wav")! - newTestCase.expectedResults = testCase[1] as! [String:[[Double]]] + newTestCase.expectedResults = testCase[1] as! [String: [[Double]]] xcTestSuite.addTest(newTestCase) } } diff --git a/binding/ios/OctopusAppTest/PerformanceTest/PerformanceTest.swift b/binding/ios/OctopusAppTest/PerformanceTest/PerformanceTest.swift index 552d70c..4ce9b2a 100644 --- a/binding/ios/OctopusAppTest/PerformanceTest/PerformanceTest.swift +++ b/binding/ios/OctopusAppTest/PerformanceTest/PerformanceTest.swift @@ -31,9 +31,9 @@ class PerformanceTest: XCTestCase { try XCTSkipIf(indexPerformanceThresholdSec == nil) let bundle = Bundle(for: type(of: self)) - let fileURL:URL = bundle.url(forResource: "multiple_keywords", withExtension: "wav")! + let fileURL: URL = bundle.url(forResource: "multiple_keywords", withExtension: "wav")! let audioData = try Data(contentsOf: fileURL) - var pcm = Array(repeating: 0, count: (audioData.count - 44) / 2) + var pcm = [Int16](repeating: 0, count: (audioData.count - 44) / 2) _ = pcm.withUnsafeMutableBytes { audioData.copyBytes(to: $0, from: 44..(repeating: 0, count: (audioData.count - 44) / 2) + var pcm = [Int16](repeating: 0, count: (audioData.count - 44) / 2) _ = pcm.withUnsafeMutableBytes { audioData.copyBytes(to: $0, from: 44.. = ["gorilla", "terminator"] var results: [Double] = [] @@ -90,7 +90,7 @@ class PerformanceTest: XCTestCase { } metadata.delete() octopus.delete() - + let avgNSec = results.reduce(0.0, +) / Double(numTestIterations) let avgSec = Double(round(avgNSec * 1000) / 1000) XCTAssertLessThanOrEqual(avgSec, searchPerformanceThresholdSec!) diff --git a/binding/ios/OctopusErrors.swift b/binding/ios/OctopusErrors.swift index fe5882a..f51301d 100644 --- a/binding/ios/OctopusErrors.swift +++ b/binding/ios/OctopusErrors.swift @@ -7,8 +7,8 @@ // specific language governing permissions and limitations under the License. // -public class OctopusError : LocalizedError { - private let message: String; +public class OctopusError: LocalizedError { + private let message: String public init (_ message: String) { self.message = message @@ -25,24 +25,24 @@ public class OctopusError : LocalizedError { } } -public class OctopusMemoryError : OctopusError {} +public class OctopusMemoryError: OctopusError {} -public class OctopusIOError : OctopusError {} +public class OctopusIOError: OctopusError {} -public class OctopusInvalidArgumentError : OctopusError {} +public class OctopusInvalidArgumentError: OctopusError {} -public class OctopusStopIterationError : OctopusError {} +public class OctopusStopIterationError: OctopusError {} -public class OctopusKeyError : OctopusError {} +public class OctopusKeyError: OctopusError {} -public class OctopusInvalidStateError : OctopusError {} +public class OctopusInvalidStateError: OctopusError {} -public class OctopusRuntimeError : OctopusError {} +public class OctopusRuntimeError: OctopusError {} -public class OctopusActivationError : OctopusError {} +public class OctopusActivationError: OctopusError {} -public class OctopusActivationLimitError : OctopusError {} +public class OctopusActivationLimitError: OctopusError {} -public class OctopusActivationThrottledError : OctopusError {} +public class OctopusActivationThrottledError: OctopusError {} -public class OctopusActivationRefusedError : OctopusError {} +public class OctopusActivationRefusedError: OctopusError {} diff --git a/binding/ios/OctopusMetadata.swift b/binding/ios/OctopusMetadata.swift index 1d503e1..99e9c30 100644 --- a/binding/ios/OctopusMetadata.swift +++ b/binding/ios/OctopusMetadata.swift @@ -12,26 +12,26 @@ import Foundation public class OctopusMetadata { var handle: UnsafeMutablePointer? let numBytes: Int - + init(handle: UnsafeMutablePointer, numBytes: Int) { self.handle = handle self.numBytes = numBytes } - + /// Constructor. /// /// - Parameters: /// - metadataBytes: A byte array that was previously obtained via `getBytes` - public init(metadataBytes:[UInt8]) { + public init(metadataBytes: [UInt8]) { self.handle = UnsafeMutablePointer.allocate(capacity: metadataBytes.count) self.handle?.initialize(from: metadataBytes, count: metadataBytes.count) self.numBytes = metadataBytes.count } - + deinit { self.delete() } - + /// Releases resources acquired by `OctopusMetadata` public func delete() { if handle != nil { @@ -39,7 +39,7 @@ public class OctopusMetadata { handle = nil } } - + /// Gets a binary representation of the metadata /// /// - Throws: OctopusError @@ -48,8 +48,8 @@ public class OctopusMetadata { if handle == nil { throw OctopusInvalidStateError("Octopus metadata has been deleted") } - - return Array(UnsafeBufferPointer(start: handle, count:numBytes)) + + return Array(UnsafeBufferPointer(start: handle, count: numBytes)) } - + } diff --git a/demo/android/OctopusDemo/octopus-demo-app/src/androidTest/java/ai/picovoice/octopusdemo/LanguageTests.java b/demo/android/OctopusDemo/octopus-demo-app/src/androidTest/java/ai/picovoice/octopusdemo/LanguageTests.java index 97ade13..6d528a2 100644 --- a/demo/android/OctopusDemo/octopus-demo-app/src/androidTest/java/ai/picovoice/octopusdemo/LanguageTests.java +++ b/demo/android/OctopusDemo/octopus-demo-app/src/androidTest/java/ai/picovoice/octopusdemo/LanguageTests.java @@ -40,37 +40,37 @@ public static Collection initParameters() { }, { "es", - new HashMap () {{ + new HashMap() {{ put("manzana", new double [][]{{5.184, 5.984, 1}}); }}, }, { "fr", - new HashMap () {{ + new HashMap() {{ put("perroquet", new double [][]{{4.352, 5.184, 0.990}}); }}, }, { "it", - new HashMap () {{ + new HashMap() {{ put("porcospino", new double [][]{{0.480, 1.728, 1}}); }}, }, { "ja", - new HashMap () {{ + new HashMap() {{ put("りんご", new double [][]{{0.960, 1.664, 1}}); }}, }, { "ko", - new HashMap () {{ + new HashMap() {{ put("아이스크림", new double [][]{{6.592, 7.520, 0.961}}); }}, }, { "pt", - new HashMap () {{ + new HashMap() {{ put("porco espinho", new double [][]{{0.480, 1.792, 1}}); }}, } diff --git a/demo/android/OctopusDemo/octopus-demo-app/src/androidTest/java/ai/picovoice/octopusdemo/PerformanceTest.java b/demo/android/OctopusDemo/octopus-demo-app/src/androidTest/java/ai/picovoice/octopusdemo/PerformanceTest.java index 55eee62..5ff3328 100644 --- a/demo/android/OctopusDemo/octopus-demo-app/src/androidTest/java/ai/picovoice/octopusdemo/PerformanceTest.java +++ b/demo/android/OctopusDemo/octopus-demo-app/src/androidTest/java/ai/picovoice/octopusdemo/PerformanceTest.java @@ -61,7 +61,7 @@ public void Setup() throws IOException { String iterationString = appContext.getString(R.string.numTestIterations); try { numTestIterations = Integer.parseInt(iterationString); - } catch (NumberFormatException ignored) {} + } catch (NumberFormatException ignored) { } } @Test diff --git a/demo/android/OctopusDemo/octopus-demo-app/src/main/java/ai/picovoice/octopusdemo/MainActivity.java b/demo/android/OctopusDemo/octopus-demo-app/src/main/java/ai/picovoice/octopusdemo/MainActivity.java index ba7a3da..61e5dec 100644 --- a/demo/android/OctopusDemo/octopus-demo-app/src/main/java/ai/picovoice/octopusdemo/MainActivity.java +++ b/demo/android/OctopusDemo/octopus-demo-app/src/main/java/ai/picovoice/octopusdemo/MainActivity.java @@ -102,7 +102,7 @@ protected void onCreate(Bundle savedInstanceState) { }); voiceProcessor.addErrorListener(error -> { - runOnUiThread(()-> displayError(error.toString(), Toast.LENGTH_LONG)); + runOnUiThread(() -> displayError(error.toString(), Toast.LENGTH_LONG)); }); } @@ -185,6 +185,7 @@ private void setUIState(UIState state) { searchLayout.setVisibility(View.INVISIBLE); statusTextView.setText(""); break; + default: } }); } @@ -205,13 +206,24 @@ private void displayError(String message, int toastLength) { }); } + private boolean hasRecordPermission() { + return ActivityCompat.checkSelfPermission( + this, + Manifest.permission.RECORD_AUDIO + ) == PackageManager.PERMISSION_GRANTED; + } + private void requestRecordPermission() { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 0); } @SuppressLint("SetTextI18n") @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + public void onRequestPermissionsResult( + int requestCode, + @NonNull String[] permissions, + @NonNull int[] grantResults + ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length == 0 || grantResults[0] == PackageManager.PERMISSION_DENIED) { ToggleButton toggleButton = findViewById(R.id.recordButton); @@ -307,13 +319,20 @@ public void displayMatches(OctopusMatch[] matches) { searchResultsView.setVisibility(View.VISIBLE); for (OctopusMatch match : matches) { - Log.i("OctopusDemo", String.format("%f -> %f (%f)%n", match.getStartSec(), match.getEndSec(), match.getProbability())); + Log.i( + "OctopusDemo", + String.format( + "%f -> %f (%f)%n", + match.getStartSec(), + match.getEndSec(), + match.getProbability())); } LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getApplicationContext()); searchResultsView.setLayoutManager(linearLayoutManager); - SearchResultsViewAdaptor searchResultsViewAdaptor = new SearchResultsViewAdaptor(getApplicationContext(), Arrays.asList(matches)); + SearchResultsViewAdaptor searchResultsViewAdaptor = + new SearchResultsViewAdaptor(getApplicationContext(), Arrays.asList(matches)); searchResultsView.setAdapter(searchResultsViewAdaptor); }); } @@ -372,8 +391,8 @@ private enum UIState { } public static class SearchResultsViewAdaptor extends RecyclerView.Adapter { - final private List data; - final private LayoutInflater inflater; + private final List data; + private final LayoutInflater inflater; SearchResultsViewAdaptor(Context context, List data) { this.inflater = LayoutInflater.from(context); diff --git a/demo/c/octopus_index_demo.c b/demo/c/octopus_index_demo.c index 9116c5b..77521a0 100644 --- a/demo/c/octopus_index_demo.c +++ b/demo/c/octopus_index_demo.c @@ -26,7 +26,6 @@ static void *pv_open_dl(const char *path) { return dlopen(path, RTLD_NOW); #endif - } static void *pv_load_sym(void *dl, const char *sym) { @@ -40,7 +39,6 @@ static void *pv_load_sym(void *dl, const char *sym) { return dlsym(dl, sym); #endif - } static void pv_close_dl(void *dl) { @@ -54,7 +52,6 @@ static void pv_close_dl(void *dl) { dlclose(dl); #endif - } static void print_dl_error(const char *message) { @@ -68,22 +65,21 @@ static void print_dl_error(const char *message) { fprintf(stderr, "%s with '%s'.\n", message, dlerror()); #endif - } static struct option long_options[] = { - {"library_path", required_argument, NULL, 'l'}, - {"model_path", required_argument, NULL, 'm'}, - {"access_key", required_argument, NULL, 'a'}, - {"audio_path", required_argument, NULL, 'w'}, - {"index_path", required_argument, NULL, 'i'}, + {"library_path", required_argument, NULL, 'l'}, + {"model_path", required_argument, NULL, 'm'}, + {"access_key", required_argument, NULL, 'a'}, + {"audio_path", required_argument, NULL, 'w'}, + {"index_path", required_argument, NULL, 'i'}, }; void print_usage(const char *program_name) { fprintf( - stderr, - "usage : %s -l LIBRARY_PATH -m MODEL_PATH -a ACCESS_KEY -w AUDIO_PATH -i INDEX_PATH\n", - program_name); + stderr, + "usage : %s -l LIBRARY_PATH -m MODEL_PATH -a ACCESS_KEY -w AUDIO_PATH -i INDEX_PATH\n", + program_name); } int picovoice_main(int argc, char *argv[]) { @@ -210,7 +206,7 @@ int main(int argc, char *argv[]) { #if defined(_WIN32) || defined(_WIN64) #define UTF8_COMPOSITION_FLAG (0) -#define NULL_TERMINATED (-1) +#define NULL_TERMINATED (-1) LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &argc); if (wargv == NULL) { diff --git a/demo/c/octopus_search_demo.c b/demo/c/octopus_search_demo.c index 7e1fe57..f55bd8c 100644 --- a/demo/c/octopus_search_demo.c +++ b/demo/c/octopus_search_demo.c @@ -26,7 +26,6 @@ static void *pv_open_dl(const char *path) { return dlopen(path, RTLD_NOW); #endif - } static void *pv_load_sym(void *dl, const char *sym) { @@ -40,7 +39,6 @@ static void *pv_load_sym(void *dl, const char *sym) { return dlsym(dl, sym); #endif - } static void pv_close_dl(void *dl) { @@ -54,7 +52,6 @@ static void pv_close_dl(void *dl) { dlclose(dl); #endif - } static void print_dl_error(const char *message) { @@ -68,22 +65,21 @@ static void print_dl_error(const char *message) { fprintf(stderr, "%s with '%s'.\n", message, dlerror()); #endif - } static struct option long_options[] = { - {"library_path", required_argument, NULL, 'l'}, - {"model_path", required_argument, NULL, 'm'}, - {"access_key", required_argument, NULL, 'a'}, - {"index_path", required_argument, NULL, 'i'}, - {"search_phrase", required_argument, NULL, 's'}, + {"library_path", required_argument, NULL, 'l'}, + {"model_path", required_argument, NULL, 'm'}, + {"access_key", required_argument, NULL, 'a'}, + {"index_path", required_argument, NULL, 'i'}, + {"search_phrase", required_argument, NULL, 's'}, }; void print_usage(const char *program_name) { fprintf( - stderr, - "usage : %s -l LIBRARY_PATH -m MODEL_PATH -a ACCESS_KEY -i INDEX_PATH -s SEARCH_PHRASE\n", - program_name); + stderr, + "usage : %s -l LIBRARY_PATH -m MODEL_PATH -a ACCESS_KEY -i INDEX_PATH -s SEARCH_PHRASE\n", + program_name); } int picovoice_main(int argc, char *argv[]) { @@ -215,7 +211,7 @@ int picovoice_main(int argc, char *argv[]) { gettimeofday(&after, NULL); total_cpu_time_usec += - (double) (after.tv_sec - before.tv_sec) * 1e6 + (double) (after.tv_usec - before.tv_usec); + (double) (after.tv_sec - before.tv_sec) * 1e6 + (double) (after.tv_usec - before.tv_usec); free(indices); pv_octopus_delete_func(o); @@ -242,7 +238,7 @@ int main(int argc, char *argv[]) { #if defined(_WIN32) || defined(_WIN64) #define UTF8_COMPOSITION_FLAG (0) -#define NULL_TERMINATED (-1) +#define NULL_TERMINATED (-1) LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &argc); if (wargv == NULL) { diff --git a/demo/ios/OctopusDemo/OctopusDemo.xcodeproj/xcshareddata/xcschemes/OctopusDemo.xcscheme b/demo/ios/OctopusDemo/OctopusDemo.xcodeproj/xcshareddata/xcschemes/OctopusDemo.xcscheme new file mode 100644 index 0000000..85e8eba --- /dev/null +++ b/demo/ios/OctopusDemo/OctopusDemo.xcodeproj/xcshareddata/xcschemes/OctopusDemo.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/ios/OctopusDemo/OctopusDemo/ContentView.swift b/demo/ios/OctopusDemo/OctopusDemo/ContentView.swift index 4b2795c..338a8da 100644 --- a/demo/ios/OctopusDemo/OctopusDemo/ContentView.swift +++ b/demo/ios/OctopusDemo/OctopusDemo/ContentView.swift @@ -10,15 +10,15 @@ import SwiftUI struct ContentView: View { @StateObject var viewModel = ViewModel() - + let activeBlue = Color(red: 55/255, green: 125/255, blue: 1, opacity: 1) let detectionBlue = Color(red: 0, green: 229/255, blue: 195/255, opacity: 1) let dangerRed = Color(red: 1, green: 14/255, blue: 14/255, opacity: 1) - + var body: some View { let interactDisabled: Bool = viewModel.state == UIState.FATAL_ERROR || viewModel.isBusy - - VStack(alignment: .center){ + + VStack(alignment: .center) { Spacer() ZStack(alignment: .center) { Text(String(format: "%.1f", viewModel.recordingTimeSec)) @@ -27,10 +27,10 @@ struct ContentView: View { .opacity(viewModel.state == UIState.RECORDING ? 1 : 0) ProgressView("Indexing...") .progressViewStyle(CircularProgressViewStyle(tint: activeBlue)) - .scaleEffect(x:2, y:2, anchor: .center) + .scaleEffect(x: 2, y: 2, anchor: .center) .opacity(viewModel.state == UIState.INDEXING ? 1 : 0) .padding(20) - + VStack(alignment: .center) { Text(viewModel.searchResultCountText) .font(.system(size: 25.0)) @@ -89,7 +89,7 @@ struct ContentView: View { Spacer() Spacer() Spacer() - + HStack(alignment: .center) { TextField("Search Phrase", text: $viewModel.searchPhraseText, @@ -100,14 +100,14 @@ struct ContentView: View { .cornerRadius(8) .disableAutocorrection(true) .autocapitalization(.none) - .overlay(HStack() { + .overlay(HStack { Image(systemName: "magnifyingglass") .foregroundColor(.gray) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) .padding(.leading, 8) }) .disabled(interactDisabled) - Button(action: viewModel.searchMetadata){ + Button(action: viewModel.searchMetadata) { Text("Search") }.disabled(interactDisabled) } @@ -115,7 +115,7 @@ struct ContentView: View { .opacity(viewModel.state == UIState.NEW_SEARCH || viewModel.state == UIState.SEARCH_RESULTS || viewModel.state == UIState.ZERO_SEARCH_RESULTS ? 1 : 0) - + Text(viewModel.errorMessage) .frame(minWidth: 0, maxWidth: UIScreen.main.bounds.width - 50) .padding() @@ -124,13 +124,13 @@ struct ContentView: View { .foregroundColor(.white) .cornerRadius(.infinity) .opacity(viewModel.state == UIState.FATAL_ERROR ? 1 : 0) - + Spacer() Text(viewModel.statusText) .padding(4) .foregroundColor(.gray) .multilineTextAlignment(.center) - Button(action: viewModel.toggleRecording){ + Button(action: viewModel.toggleRecording) { Text(viewModel.state == UIState.RECORDING ? "STOP" : "RECORD") .font(.title) .background(interactDisabled ? Color.gray : activeBlue) @@ -162,4 +162,3 @@ struct ContentView_Previews: PreviewProvider { } } } - diff --git a/demo/ios/OctopusDemo/OctopusDemo/ViewModel.swift b/demo/ios/OctopusDemo/OctopusDemo/ViewModel.swift index 15d419a..13defe0 100644 --- a/demo/ios/OctopusDemo/OctopusDemo/ViewModel.swift +++ b/demo/ios/OctopusDemo/OctopusDemo/ViewModel.swift @@ -22,29 +22,29 @@ enum UIState { } class ViewModel: ObservableObject { - + private let ACCESS_KEY = "{YOUR_ACCESS_KEY_HERE}" - - private var octopus:Octopus! - private var metadata:OctopusMetadata! - + + private var octopus: Octopus! + private var metadata: OctopusMetadata! + private var recordingTimer = Timer() private var audioRecorder: AVAudioRecorder! private var isListening = false private let MAX_RECORDING_LENGTH_SEC = 120.0 - - @Published var recordToggleButtonText:String = "Start" - @Published var searchPhraseText:String = "" - @Published var results:[OctopusMatch] = [] + + @Published var recordToggleButtonText: String = "Start" + @Published var searchPhraseText: String = "" + @Published var results: [OctopusMatch] = [] @Published var statusText = "" @Published var showErrorAlert = false @Published var errorAlertMessage = "" @Published var errorMessage = "" - @Published var state:UIState = UIState.INTRO - @Published var isBusy:Bool = false - @Published var searchResultCountText:String = "# matches found" + @Published var state: UIState = UIState.INTRO + @Published var isBusy: Bool = false + @Published var searchResultCountText: String = "# matches found" @Published var recordingTimeSec = 0.0 - + init() { isBusy = true do { @@ -59,13 +59,13 @@ class ViewModel: ObservableObject { errorMessage = "ACCESS_KEY activation refused" } catch is OctopusActivationLimitError { errorMessage = "ACCESS_KEY reached its limit" - } catch is OctopusActivationThrottledError { + } catch is OctopusActivationThrottledError { errorMessage = "ACCESS_KEY is throttled" } catch { errorMessage = "\(error)" } } - + deinit { do { try stop() @@ -74,12 +74,12 @@ class ViewModel: ObservableObject { } octopus.delete() } - - func onOctopusInitFail(_ initError:String) { + + func onOctopusInitFail(_ initError: String) { errorMessage = initError state = UIState.FATAL_ERROR } - + public func toggleRecording() { if isListening { toggleRecordingOff() @@ -87,13 +87,13 @@ class ViewModel: ObservableObject { toggleRecordingOn() } } - + public func toggleRecordingOff() { recordingTimer.invalidate() statusText = "" state = UIState.INDEXING isBusy = true - + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { do { try self.stop() @@ -107,18 +107,18 @@ class ViewModel: ObservableObject { self.isBusy = false } } - - public func toggleRecordingOn(){ + + public func toggleRecordingOn() { isBusy = true recordingTimeSec = 0 - recordingTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in + recordingTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in self.recordingTimeSec += 0.1 - if(self.recordingTimeSec > self.MAX_RECORDING_LENGTH_SEC) { + if self.recordingTimeSec > self.MAX_RECORDING_LENGTH_SEC { self.toggleRecordingOff() self.showErrorAlert("Recording exceeded max recording length") } } - + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { do { try self.start() @@ -130,18 +130,18 @@ class ViewModel: ObservableObject { self.isBusy = false } } - - public func searchMetadata(){ - + + public func searchMetadata() { + let resign = #selector(UIResponder.resignFirstResponder) UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil) - + searchPhraseText = searchPhraseText.trimmingCharacters(in: .whitespacesAndNewlines) guard !searchPhraseText.isEmpty else { showErrorAlert("Search phrase cannot be empty") return } - + do { let searchResults = try octopus.search(metadata: metadata, phrases: Set([searchPhraseText])) for (_, matches) in searchResults { @@ -149,7 +149,7 @@ class ViewModel: ObservableObject { for result in results { print(result) } - + statusText = "" if results.count == 0 { searchResultCountText = "No matches found" @@ -160,24 +160,24 @@ class ViewModel: ObservableObject { state = UIState.SEARCH_RESULTS } } - + } catch let error as OctopusInvalidArgumentError { showErrorAlert("\(error)") } catch { showErrorAlert("\(error)") } } - + public func showErrorAlert(_ message: String) { errorAlertMessage = message showErrorAlert = true } - + public func start() throws { guard !isListening else { return } - + let audioSession = AVAudioSession.sharedInstance() if audioSession.recordPermission == .denied { errorMessage = "Recording permission is required for this demo" @@ -185,14 +185,14 @@ class ViewModel: ObservableObject { statusText = "" return } - + try audioSession.setActive(true) try audioSession.setCategory(AVAudioSession.Category.playAndRecord, options: [.mixWithOthers, .defaultToSpeaker, .allowBluetooth]) - + let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let audioFilename = documentPath.appendingPathComponent("OctopusDemo.wav") - + var formatDescription = AudioStreamBasicDescription( mSampleRate: Float64(Octopus.pcmDataSampleRate), mFormatID: kAudioFormatLinearPCM, @@ -204,24 +204,26 @@ class ViewModel: ObservableObject { mBitsPerChannel: 16, mReserved: 0) let format = AVAudioFormat(streamDescription: &formatDescription)! - + audioRecorder = try AVAudioRecorder(url: audioFilename, format: format) audioRecorder.record() isListening = true } - + public func stop() throws { guard isListening else { return } - + audioRecorder.stop() isListening = false - + let fileManager = FileManager.default let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] - let directoryContents = try fileManager.contentsOfDirectory(at: documentDirectory, includingPropertiesForKeys: nil) - + let directoryContents = try fileManager.contentsOfDirectory( + at: documentDirectory, + includingPropertiesForKeys: nil) + let path = directoryContents[0].path metadata = try octopus.indexAudioFile(path: path) } diff --git a/demo/ios/OctopusDemo/Podfile.lock b/demo/ios/OctopusDemo/Podfile.lock index 779965c..5707c36 100644 --- a/demo/ios/OctopusDemo/Podfile.lock +++ b/demo/ios/OctopusDemo/Podfile.lock @@ -13,4 +13,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 46c66a375b591b3da119ecbb2bcfa7ea00d33654 -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/demo/python/octopus_demo.py b/demo/python/octopus_demo.py index ff0aa4f..800578a 100644 --- a/demo/python/octopus_demo.py +++ b/demo/python/octopus_demo.py @@ -1,5 +1,5 @@ # -# Copyright 2021-2022 Picovoice Inc. +# Copyright 2021-2023 Picovoice Inc. # # You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" # file accompanying this source. @@ -63,6 +63,12 @@ def main(): help='AccessKey provided by Picovoice Console (https://console.picovoice.ai/)', required=True) + parser.add_argument( + '--search_phrase', + help='Phrase to search in the provided audio paths', + default=None + ) + args = parser.parse_args() try: @@ -90,8 +96,10 @@ def main(): indexing_animation.stop() try: + search_phrase = args.search_phrase while True: - search_phrase = input("\rEnter search phrase (Ctrl+c to exit): ") + if args.search_phrase is None: + search_phrase = input("\rEnter search phrase (Ctrl+c to exit): ") search_phrase = search_phrase.strip() for i, metadata in enumerate(metadata_list): try: @@ -110,6 +118,9 @@ def main(): print("Nothing found!") print("\n") + if args.search_phrase is not None: + break + except KeyboardInterrupt: print('Stopping ...') finally: diff --git a/include/picovoice.h b/include/picovoice.h index 147f6be..3631832 100644 --- a/include/picovoice.h +++ b/include/picovoice.h @@ -54,7 +54,6 @@ typedef enum { PV_API const char *pv_status_to_string(pv_status_t status); #ifdef __cplusplus - } #endif diff --git a/include/pv_octopus.h b/include/pv_octopus.h index a93abe9..8c7ed35 100644 --- a/include/pv_octopus.h +++ b/include/pv_octopus.h @@ -38,7 +38,7 @@ typedef struct pv_octopus pv_octopus_t; * 'PV_STATUS_ACTIVATION_THROTTLED', or 'PV_STATUS_ACTIVATION_REFUSED' on failure */ PV_API pv_status_t pv_octopus_init( - const char* access_key, + const char *access_key, const char *model_path, pv_octopus_t **object); diff --git a/res/.lint/c/formatter.py b/res/.lint/c/formatter.py new file mode 100644 index 0000000..397dcbf --- /dev/null +++ b/res/.lint/c/formatter.py @@ -0,0 +1,78 @@ +import fnmatch +import os +import re +import subprocess +from argparse import ArgumentParser + +import sys + +IGNORE_LIST = { + 'lib' +} + +EXCLUDE_PATTERN = { + '.*ios.*', + '.*node_modules.*', + '.*build.*', +} + + +def main(): + parser = ArgumentParser() + + parser.add_argument('--verbose', + '-v', + action='store_true', + help='If set, shows the list of processed files') + parser.add_argument('--check-only', + '-c', + action='store_true', + help='If set, checks for warnings only') + + input_args = parser.parse_args() + formatter(input_args.verbose, input_args.check_only) + + +def formatter(verbose, check_only): + if check_only: + cmd = "clang-format --dry-run --Werror --verbose" + else: + cmd = "clang-format -i -style=file --verbose" + print(cmd) + + src_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../../../') + all_files = find('*.c', src_dir) + all_files.extend(find('*.h', src_dir)) + + c_source_files = [file_path for file_path in all_files if + not any(ignored_path in file_path for ignored_path in IGNORE_LIST)] + + c_source_files = [file_path for file_path in c_source_files if + not any(re.match(pattern, file_path, flags=re.IGNORECASE) for pattern in EXCLUDE_PATTERN)] + + c_source_files_num = len(c_source_files) + for index, c_source_file in enumerate(c_source_files): + format_command = f"{cmd} {c_source_file}" + try: + result = subprocess.check_output(format_command, shell=True, stderr=subprocess.STDOUT).decode('utf-8') + except subprocess.CalledProcessError as e: + print(f'Formatter failed with ({e.returncode}):\n{e.output.decode("utf-8")}') + continue + + if verbose: + print(result) + sys.stdout.write(f"Completion: {index / c_source_files_num * 100:.2f}%\r") + sys.stdout.flush() + + +def find(pattern, path): + file_list = [] + for root, dirs, files in os.walk(path): + for name in files: + if fnmatch.fnmatch(name, pattern): + file_list.append(os.path.join(root, name)) + return file_list + + +if __name__ == '__main__': + main() diff --git a/res/.lint/java/checkstyle-10.5.0-all.jar b/res/.lint/java/checkstyle-10.5.0-all.jar new file mode 100644 index 0000000..2dc5288 Binary files /dev/null and b/res/.lint/java/checkstyle-10.5.0-all.jar differ diff --git a/res/.lint/java/checkstyle.xml b/res/.lint/java/checkstyle.xml new file mode 100644 index 0000000..6bd226e --- /dev/null +++ b/res/.lint/java/checkstyle.xml @@ -0,0 +1,360 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/.lint/java/suppress.xml b/res/.lint/java/suppress.xml new file mode 100644 index 0000000..66f7d34 --- /dev/null +++ b/res/.lint/java/suppress.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/spell-check/.cspell.json b/res/.lint/spell-check/.cspell.json similarity index 84% rename from res/spell-check/.cspell.json rename to res/.lint/spell-check/.cspell.json index 52a22a2..416256c 100644 --- a/res/spell-check/.cspell.json +++ b/res/.lint/spell-check/.cspell.json @@ -11,12 +11,6 @@ } ], "ignorePaths": [ - // submodules - "../../demo/c/dr_libs/**/*", - "../../demo/c/pvrecorder/**/*", - "../../demo/mcu/psoc062s2/**/*", - "../../lib/mcu/psoc062s2/**/*", - // binaries "**/*.a", "**/*.dll", @@ -29,7 +23,7 @@ "**/*.so", "**/*.wasm", "**/*.wav", - "../../lib/ios/**/*", + "../../../lib/ios/**/*", // android & java "**/AndroidManifest.xml", diff --git a/res/spell-check/dict.txt b/res/.lint/spell-check/dict.txt similarity index 100% rename from res/spell-check/dict.txt rename to res/.lint/spell-check/dict.txt diff --git a/res/.lint/swift/.swiftlint.yml b/res/.lint/swift/.swiftlint.yml new file mode 100644 index 0000000..1b32beb --- /dev/null +++ b/res/.lint/swift/.swiftlint.yml @@ -0,0 +1,10 @@ +disabled_rules: + - identifier_name + - function_body_length + - force_cast + - implicit_getter + - cyclomatic_complexity + - function_parameter_count +excluded: + - ${PWD}/**/Pods + - ${PWD}/**/node_modules