diff --git a/Sources/OEVoice/AVSpeechSynthesizer-IPASpeech-extension.swift b/Sources/OEVoice/AVSpeechSynthesizer+speakIPA.swift similarity index 93% rename from Sources/OEVoice/AVSpeechSynthesizer-IPASpeech-extension.swift rename to Sources/OEVoice/AVSpeechSynthesizer+speakIPA.swift index 03e9b56..63d9fb3 100644 --- a/Sources/OEVoice/AVSpeechSynthesizer-IPASpeech-extension.swift +++ b/Sources/OEVoice/AVSpeechSynthesizer+speakIPA.swift @@ -1,6 +1,6 @@ // -// AVSpeechSynthesizer-IPASpeech-extension.swift -// Wordhord +// AVSpeechSynthesizer+speakIPA.swift +// OEVoice // // Created by Ryan Lintott on 2020-07-28. // @@ -9,7 +9,7 @@ import Foundation import AVFoundation @available(iOS 10.0, *) -extension AVSpeechSynthesizer { +public extension AVSpeechSynthesizer { func speakIPA(_ ipaString: String, voiceIdentifier: String, willSpeak: ((String) -> Void)? = nil) { //Set the audio session to playback to ignore mute switch on device do { diff --git a/Sources/OEVoice/OEVoice+AVFoundation.swift b/Sources/OEVoice/OEVoice+AVFoundation.swift new file mode 100644 index 0000000..7aacd62 --- /dev/null +++ b/Sources/OEVoice/OEVoice+AVFoundation.swift @@ -0,0 +1,51 @@ +// +// OEVoice+AVFoundation.swift +// OEVoice +// +// Created by Ryan Lintott on 2021-07-29. +// + +import Foundation +import AVFoundation + +@available(iOS 10.0, *) +extension OEVoice { + public var shortIdentifier: String { + switch self { + case .siriMarthaGBcompact: + if #available(iOS 14.5, *) { + return "siri_Martha_en-GB_compact" + } else { + return "siri_female_en-GB_compact" + } + case .siriArthurGBcompact: + if #available(iOS 14.5, *) { + return "siri_Aurthur_en-GB_compact" + } else { + return "siri_male_en-GB_compact" + } + case .siriNickyUScompact: + if #available(iOS 14.5, *) { + return "siri_Nicky_en-US_compact" + } else { + return "siri_female_en-US_compact" + } + case .siriAaronUScompact: + if #available(iOS 14.5, *) { + return "siri_Aaron_en-US_compact" + } else { + return "siri_male_en-US_compact" + } + case .danielGBcompact: + return "Daniel-compact" + } + } + + public var identifier: String { + return "com.apple.ttsbundle.".appending(shortIdentifier) + } + + public var voice: AVSpeechSynthesisVoice? { + AVSpeechSynthesisVoice(identifier: identifier) + } +} diff --git a/Sources/OEVoice/OEVoice.swift b/Sources/OEVoice/OEVoice.swift index a76c506..166466b 100644 --- a/Sources/OEVoice/OEVoice.swift +++ b/Sources/OEVoice/OEVoice.swift @@ -10,64 +10,38 @@ import AVFoundation @available(iOS 10.0, *) public enum OEVoice: CaseIterable { - // Default UK voices - Daniel - // Default UK siri voices - Martha, Arthur - // Default US voices - Fred, Samantha - // Default US siri voices - Nicky, Aaron - // Extra UK voices - Kate, Oliver, Serena - // Extra US voices - Alex, Allison, Ava, Nicky, Susan, Tom, Victoria + + // Default UK voice + case danielGBcompact + + // Default UK Siri voices (not available on macOS or simulator) case siriMarthaGBcompact case siriArthurGBcompact - case siriNickyUScompact - case siriAaronUScompact + + // Premium UK Siri voices (not on device by default and not available on macOS or simulator) // case siriMarthaGBpremium // case siriArthurGBpremium + + // Extra UK voices - Kate, Oliver, Serena (not on device by default) + + // Default US voices - Fred, Samantha + + // Default US Siri voices (not available on macOS or simulator) + case siriNickyUScompact + case siriAaronUScompact + + // Premium US Siri voices (not on device by default and not available on macOS or simulator) // case siriNickyUSpremium // case siriAaronUSpremium - case danielGBcompact + + // Extra US voices - Alex, Allison, Ava, Nicky, Susan, Tom, Victoria (not on device by default) #if targetEnvironment(simulator) - static let `default` = OEVoice.danielGBcompact + public static let `default` = OEVoice.danielGBcompact #else - static let `default` = OEVoice.siriNickyUScompact + public static let `default` = OEVoice.siriNickyUScompact #endif - private var identifier: String { - let prefix = "com.apple.ttsbundle." - switch self { - case .siriMarthaGBcompact: - return prefix.appending("siri_Martha_en-GB_compact") - case .siriArthurGBcompact: - return prefix.appending("siri_Aurthur_en-GB_compact") - case .siriNickyUScompact: - return prefix.appending("siri_Nicky_en-US_compact") - case .siriAaronUScompact: - return prefix.appending("siri_Aaron_en-US_compact") - case .danielGBcompact: - return prefix.appending("Daniel-compact") - } - } - - private var legacyIdentifiers: [String] { - let prefix = "com.apple.ttsbundle." - switch self { - case .siriMarthaGBcompact: - return [prefix.appending("siri_female_en-GB_compact")] - case .siriArthurGBcompact: - return [prefix.appending("siri_male_en-GB_compact")] - case .siriNickyUScompact: - return [prefix.appending("siri_female_en-US_compact")] - case .siriAaronUScompact: - return [prefix.appending("siri_male_en-US_compact")] - default: - return [] - } - } - - public var voice: AVSpeechSynthesisVoice? { - AVSpeechSynthesisVoice(identifier: identifier) ?? legacyIdentifiers.compactMap { AVSpeechSynthesisVoice(identifier: $0) }.first - } - public func speak(_ ipaString: String, synthesizer: AVSpeechSynthesizer) { synthesizer.speakIPA(adjustIPAString(ipaString), voiceIdentifier: identifier) } diff --git a/Sources/OEVoice/SpecialCharacter.swift b/Sources/OEVoice/SpecialCharacter.swift index 6288fde..f2e5384 100644 --- a/Sources/OEVoice/SpecialCharacter.swift +++ b/Sources/OEVoice/SpecialCharacter.swift @@ -1,6 +1,6 @@ // // SpecialCharacter.swift -// TextToSpeech +// OEVoice // // Created by Ryan Lintott on 2021-06-23. // diff --git a/Sources/OEVoice/extensions/String-extensions.swift b/Sources/OEVoice/extensions/String-extensions.swift new file mode 100644 index 0000000..4e12699 --- /dev/null +++ b/Sources/OEVoice/extensions/String-extensions.swift @@ -0,0 +1,30 @@ +// +// String-extensions.swift +// Wordhord +// +// Created by Ryan Lintott on 2020-11-06. +// + +import SwiftUI + +internal extension String { + func droppingSuffix(_ string: S) -> String { + guard self.hasSuffix(string) else { return String(self) } + return String(self.dropLast(string.count)) + } + + func droppingPrefix(_ string: S) -> String { + guard self.hasPrefix(string) else { return String(self) } + return String(self.dropFirst(string.count)) + } + + func replacingSuffixOccurrence(of string: String, with replacement: String) -> String { + guard self.hasSuffix(string) else { return self } + return self.droppingSuffix(string).appending(replacement) + } + + func replacingPrefixOccurrence(of string: String, with replacement: String) -> String { + guard self.hasPrefix(string) else { return self } + return replacement.appending(self.droppingPrefix(string)) + } +}