Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions apps/ios/GuideDogs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@
91172A732AD8D56D00E6E8E9 /* CoreGPX in Frameworks */ = {isa = PBXBuildFile; productRef = 91172A722AD8D56D00E6E8E9 /* CoreGPX */; };
914BAAF32AD745E400CB2171 /* DestinationManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914BAAF22AD745E400CB2171 /* DestinationManagerTest.swift */; };
914BAAFD2AD7483300CB2171 /* AudioEngineTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914BAAFC2AD7483300CB2171 /* AudioEngineTest.swift */; };
F100000131A2000100C0FFEE /* VoiceCatalogueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F100000031A2000100C0FFEE /* VoiceCatalogueTests.swift */; };
914BAB012AD7490100CB2171 /* NavigationControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914BAB002AD7490100CB2171 /* NavigationControllerTest.swift */; };
D4A72B0131A1000100C0FFEE /* URLResourceIdentifierTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A72B0031A1000100C0FFEE /* URLResourceIdentifierTest.swift */; };
D4A72B1131A2000100C0FFEE /* LocalizationContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A72B1031A2000100C0FFEE /* LocalizationContextTest.swift */; };
Expand Down Expand Up @@ -605,8 +606,6 @@
C30DDADF2255796B00104085 /* NotificationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30DDADE2255796B00104085 /* NotificationProtocol.swift */; };
C30DDAE9225684BC00104085 /* GDASpatialDataResultEntity+Typeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30DDAE8225684BC00104085 /* GDASpatialDataResultEntity+Typeable.swift */; };
C30DDAEC2256935F00104085 /* Typeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30DDAEB2256935F00104085 /* Typeable.swift */; };
C30EF61F2089475D00BEA785 /* VoiceSettingsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30EF61E2089475D00BEA785 /* VoiceSettingsTableViewController.swift */; };
C30EF6212089481100BEA785 /* SpeakingRateTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30EF6202089481100BEA785 /* SpeakingRateTableViewCell.swift */; };
C30EF62320894AD400BEA785 /* VoiceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30EF62220894AD400BEA785 /* VoiceTableViewCell.swift */; };
C317F24E2370E9AF000579BA /* NotificationContainerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C317F24D2370E9AF000579BA /* NotificationContainerManager.swift */; };
C317F26523722ECF000579BA /* NotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C317F26423722ECF000579BA /* NotificationObserver.swift */; };
Expand Down Expand Up @@ -748,6 +747,7 @@
D29832961E4E249700352A5A /* GeoJsonGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29832951E4E249700352A5A /* GeoJsonGeometry.swift */; };
D30355D31B61401D00E1DDED /* GDAJSONObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D30355D21B61401D00E1DDED /* GDAJSONObject.m */; };
D395B0F71B41DA15005A6407 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D395B0F61B41DA15005A6407 /* Images.xcassets */; };
D4A72B0131A1000100C0FFEE /* URLResourceIdentifierTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A72B0031A1000100C0FFEE /* URLResourceIdentifierTest.swift */; };
DCAAD87F2BC1E95F0018D135 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = DCAAD87E2BC1E95F0018D135 /* PrivacyInfo.xcprivacy */; };
DCE4D2AD2D7CE40F00B5DA4B /* NewFeatures.json in Resources */ = {isa = PBXBuildFile; fileRef = DCE4D2AC2D7CE40F00B5DA4B /* NewFeatures.json */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -1323,6 +1323,7 @@
6A4891BA2A5E66DE0002D146 /* ExternalNavigationApps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ExternalNavigationApps.swift; path = Code/App/ExternalNavigationApps.swift; sourceTree = "<group>"; };
914BAAF22AD745E400CB2171 /* DestinationManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DestinationManagerTest.swift; sourceTree = "<group>"; };
914BAAFC2AD7483300CB2171 /* AudioEngineTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioEngineTest.swift; sourceTree = "<group>"; };
F100000031A2000100C0FFEE /* VoiceCatalogueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoiceCatalogueTests.swift; sourceTree = "<group>"; };
914BAB002AD7490100CB2171 /* NavigationControllerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationControllerTest.swift; sourceTree = "<group>"; };
D4A72B0031A1000100C0FFEE /* URLResourceIdentifierTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLResourceIdentifierTest.swift; sourceTree = "<group>"; };
D4A72B1031A2000100C0FFEE /* LocalizationContextTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationContextTest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1406,8 +1407,6 @@
C30DDADE2255796B00104085 /* NotificationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = NotificationProtocol.swift; path = Code/Notifications/Notifications/NotificationProtocol.swift; sourceTree = "<group>"; };
C30DDAE8225684BC00104085 /* GDASpatialDataResultEntity+Typeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "GDASpatialDataResultEntity+Typeable.swift"; path = "Code/Data/Models/Extensions/OSM Entity/GDASpatialDataResultEntity+Typeable.swift"; sourceTree = "<group>"; };
C30DDAEB2256935F00104085 /* Typeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Typeable.swift; path = Code/Data/Models/Helpers/Types/Typeable.swift; sourceTree = "<group>"; };
C30EF61E2089475D00BEA785 /* VoiceSettingsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = VoiceSettingsTableViewController.swift; path = "Code/Visual UI/View Controllers/Settings/Voice/VoiceSettingsTableViewController.swift"; sourceTree = "<group>"; };
C30EF6202089481100BEA785 /* SpeakingRateTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SpeakingRateTableViewCell.swift; path = "Code/Visual UI/Controls/Voice Settings/SpeakingRateTableViewCell.swift"; sourceTree = "<group>"; };
C30EF62220894AD400BEA785 /* VoiceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = VoiceTableViewCell.swift; path = "Code/Visual UI/Controls/Voice Settings/VoiceTableViewCell.swift"; sourceTree = "<group>"; };
C317F24D2370E9AF000579BA /* NotificationContainerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = NotificationContainerManager.swift; path = Code/Notifications/NotificationContainerManager.swift; sourceTree = "<group>"; };
C317F26423722ECF000579BA /* NotificationObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = NotificationObserver.swift; path = Code/Notifications/Observers/NotificationObserver.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1562,6 +1561,7 @@
D3CE458E1C1737AE00AC1A25 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
D3D7CFC81B3D96470020B5E9 /* Soundscape.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Soundscape.app; sourceTree = BUILT_PRODUCTS_DIR; };
D3E2A6361CAD5CA100A5192A /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
D4A72B0031A1000100C0FFEE /* URLResourceIdentifierTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLResourceIdentifierTest.swift; sourceTree = "<group>"; };
DCAAD87E2BC1E95F0018D135 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Assets/PropertyLists/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
DCE09DD62E54AC9500B66BDF /* fa-IR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fa-IR"; path = "fa-IR.lproj/Localizable.strings"; sourceTree = "<group>"; };
DCE09DD72E54AE7400B66BDF /* fa-IR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fa-IR"; path = "fa-IR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1614,7 +1614,6 @@
31D62B811DFD9B8800C5A2D8 /* ThirdPartyNoticesViewController.swift */,
31ACEE4D20B776D700916B9B /* VersionHistoryTableViewController.swift */,
287D3E8C22DE50340084B92B /* StatusTableViewController.swift */,
C30EF61E2089475D00BEA785 /* VoiceSettingsTableViewController.swift */,
B92EA05D27AC80E900127A9A /* SiriShortcutsTableViewController.swift */,
);
name = Settings;
Expand Down Expand Up @@ -3502,6 +3501,7 @@
isa = PBXGroup;
children = (
914BAAFC2AD7483300CB2171 /* AudioEngineTest.swift */,
F100000031A2000100C0FFEE /* VoiceCatalogueTests.swift */,
);
path = Audio;
sourceTree = "<group>";
Expand Down Expand Up @@ -3719,7 +3719,6 @@
isa = PBXGroup;
children = (
C33084DD209D1094007FF6DC /* Voice Cells */,
C30EF6202089481100BEA785 /* SpeakingRateTableViewCell.swift */,
C30EF62220894AD400BEA785 /* VoiceTableViewCell.swift */,
C338FF0B20AA2312003BEBF2 /* VoiceTableViewCellDelegate.swift */,
);
Expand Down Expand Up @@ -4735,6 +4734,7 @@
files = (
915FF9F62ADE4BAF002B3690 /* AuthoredActivityContentTest.swift in Sources */,
914BAAFD2AD7483300CB2171 /* AudioEngineTest.swift in Sources */,
F100000131A2000100C0FFEE /* VoiceCatalogueTests.swift in Sources */,
914BAAF32AD745E400CB2171 /* DestinationManagerTest.swift in Sources */,
91C82ABE2A6B08500086D126 /* RouteGuidanceTest.swift in Sources */,
AC3A3EE22C40C9080061EEB8 /* NearbyTableFilterTest.swift in Sources */,
Expand Down Expand Up @@ -4976,7 +4976,6 @@
B9243A7024E1DAC40044ECC6 /* DirectionRange.swift in Sources */,
62D73709249A935D00BA0A6D /* LocationDetail.swift in Sources */,
6249500326FBE247008D842B /* RouteEditViewRepresentable.swift in Sources */,
C30EF61F2089475D00BEA785 /* VoiceSettingsTableViewController.swift in Sources */,
62A61232243F9542008A8174 /* MPEditAnnotationTableViewCell.swift in Sources */,
31B94363212B6E4400AB12B2 /* GDATelemetry.swift in Sources */,
31FE6C78233A7B760098D8C5 /* TelemetryHelper.swift in Sources */,
Expand Down Expand Up @@ -5224,7 +5223,6 @@
623D3C35264C592F0005F405 /* RouteWaypoint.swift in Sources */,
31ACEE4E20B776D700916B9B /* VersionHistoryTableViewController.swift in Sources */,
622CAFA026AB38A900F804D6 /* BeaconTitleViewController.swift in Sources */,
C30EF6212089481100BEA785 /* SpeakingRateTableViewCell.swift in Sources */,
C37E33D32367C11C0033D640 /* HeadsetCalibrationModalViewObserver.swift in Sources */,
28EB9C79237CC7D90064E7EF /* ExperimentServiceModel.swift in Sources */,
B9D3FF951E11AF7300501DA3 /* LocalizationContext.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,13 @@
"voice.settings.speaking_rate" = "Speaking Rate";


/* */
"voice.settings.other_voices" = "Other Voices";

/* */
"voice.settings.novelty" = "Novelty";


/* */
"voice.voices" = "Voices";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,12 @@
/* Setting for the rate of speech. The speed of the text-to-speech voice. */
"voice.settings.speaking_rate" = "Speaking Rate";

/* Label for a collapsed group containing voices from providers other than Eloquence and eSpeak */
"voice.settings.other_voices" = "Other Voices";

/* Label for the collapsed group containing novelty speech voices */
"voice.settings.novelty" = "Novelty";

/* Screen title to select a speech voice to for use. */
"voice.voices" = "Voices";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,36 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//

import AVFoundation

extension AVSpeechSynthesisVoice {
/// Checks if there is an enhanced version of this voice available. If this voice is
/// already is an enhanced voice, this method will return false.
///
/// - Returns: True if there is an enhanced voice available, False otherwise.
func hasEnhancedVersion() -> Bool {
guard identifier.contains("com.apple.ttsbundle"), identifier.contains("compact"), !identifier.contains("siri") else {
return false
}

return true
}

/// Checks if this voice should have an enhanced version and if that enhanced version is already downloaded
///
/// - Returns: True if this is a compact voice and there is an available voice with the premium version of this voice's identifier
func hasEnhancedVersionDownloaded() -> Bool {
guard hasEnhancedVersion() else {
return false
static let knownNoveltyVoiceIdentifiers: Set<String> = [
"com.apple.speech.synthesis.voice.Albert",
"com.apple.speech.synthesis.voice.BadNews",
"com.apple.speech.synthesis.voice.Bahh",
"com.apple.speech.synthesis.voice.Bells",
"com.apple.speech.synthesis.voice.Boing",
"com.apple.speech.synthesis.voice.Bubbles",
"com.apple.speech.synthesis.voice.Cellos",
"com.apple.speech.synthesis.voice.Deranged",
"com.apple.speech.synthesis.voice.GoodNews",
"com.apple.speech.synthesis.voice.Hysterical",
"com.apple.speech.synthesis.voice.Organ",
"com.apple.speech.synthesis.voice.Princess",
"com.apple.speech.synthesis.voice.Trinoids",
"com.apple.speech.synthesis.voice.Whisper",
"com.apple.speech.synthesis.voice.Zarvox"
]

var isNoveltyVoice: Bool {
if #available(iOS 17.0, *) {
return voiceTraits.contains(.isNoveltyVoice)
}
return AVSpeechSynthesisVoice.speechVoices().contains { $0.identifier == self.identifier.replacingOccurrences(of: "compact", with: "premium")}

return Self.knownNoveltyVoiceIdentifiers.contains(identifier)
}
}
51 changes: 4 additions & 47 deletions apps/ios/GuideDogs/Code/Audio/Text To Speech/TTSConfigHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//

Expand Down Expand Up @@ -36,53 +37,9 @@ struct TTSConfigHelper {
return voice
}

static func loadVoices(forCurrentLanguage: Bool, currentLocale: Locale) -> [AVSpeechSynthesisVoice] {
let availableVoices = AVSpeechSynthesisVoice.speechVoices().filter({ !self.disallowedVoices.contains($0.identifier) })
let enhancedVoiceIdStubs: [String] = availableVoices.compactMap { $0.quality == .enhanced ? $0.identifier.replacingOccurrences(of: "premium", with: "") : nil }
let currentIdentifier = SettingsContext.shared.voiceId

// Condense the list of voices such that we keep only the enhanced version if both an enhanced and default version exist
let condensedVoices = availableVoices.filter { (voice) -> Bool in
// Keep all enhanced quality voices
guard voice.quality == .default, voice.identifier != currentIdentifier else {
return true
}

// Remove any default quality voices which matching enhanced voices
return !enhancedVoiceIdStubs.contains(voice.identifier.replacingOccurrences(of: "compact", with: ""))
}

if forCurrentLanguage {
// Filter our voices in other languages and then sort by locale and then name, but
// ensure voices for the current locale show up first in the list
return condensedVoices.filter({ Locale(identifier: $0.language).languageCode == currentLocale.languageCode }).sorted {
let identifierA = Locale(identifier: $0.language).identifierHyphened
let identifierB = Locale(identifier: $1.language).identifierHyphened
let current = currentLocale.identifierHyphened

if identifierA == current && identifierB != current {
return true
} else if identifierA != current && identifierB == current {
return false
} else if identifierA == identifierB {
return $0.name < $1.name
} else {
return identifierA < identifierB
}
}
} else {
// Filter our voices in the current language and then sort by locale and then name, but
// ensure voices for the current locale show up first in the list
return condensedVoices.filter({ Locale(identifier: $0.language).languageCode != currentLocale.languageCode }).sorted {
let identifierA = Locale(identifier: $0.language).identifierHyphened
let identifierB = Locale(identifier: $1.language).identifierHyphened

if identifierA == identifierB {
return $0.name < $1.name
} else {
return identifierA < identifierB
}
}
static func loadVoices() -> [AVSpeechSynthesisVoice] {
AVSpeechSynthesisVoice.speechVoices().filter {
!disallowedVoices.contains($0.identifier)
}
}
}

This file was deleted.

Loading