diff --git a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings
index a2384bcb3..e6705c261 100644
--- a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings
+++ b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings
@@ -1650,6 +1650,18 @@
/* Shake callouts info */
"callouts.shake_callouts.info" = "Shake the device to repeat the last callout";
+/* Title for a toggle under Manage Callouts. Controls whether callouts include a leading sound effect. */
+"callouts.sound_effects" = "Sound Effects";
+
+/* Description for Manage Callouts sound effects toggle. */
+"callouts.sound_effects.info" = "Sounds that play before the spoken messages";
+
+/* Title for a toggle under Manage Callouts. Controls whether callouts pause between announcements. */
+"callouts.delay_between" = "Delays";
+
+/* Description for Manage Callouts delay toggle. */
+"callouts.delay_between.info" = "Short pauses between callouts";
+
/* Callouts Title, Automatic Callouts */
"callouts.automatic_callouts" = "Automatic Callouts";
diff --git a/apps/ios/GuideDogs/Code/App/Settings/AutoCalloutSettingsProvider.swift b/apps/ios/GuideDogs/Code/App/Settings/AutoCalloutSettingsProvider.swift
index e331d4bdf..b874eff8f 100644
--- a/apps/ios/GuideDogs/Code/App/Settings/AutoCalloutSettingsProvider.swift
+++ b/apps/ios/GuideDogs/Code/App/Settings/AutoCalloutSettingsProvider.swift
@@ -3,11 +3,14 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
+// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//
protocol AutoCalloutSettingsProvider: AnyObject {
var automaticCalloutsEnabled: Bool { get set }
+ var calloutSoundEffectsEnabled: Bool { get set }
+ var calloutPausesEnabled: Bool { get set }
var placeSenseEnabled: Bool { get set }
var landmarkSenseEnabled: Bool { get set }
var mobilitySenseEnabled: Bool { get set }
diff --git a/apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift b/apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift
index bca0eddd0..072b39219 100644
--- a/apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift
+++ b/apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift
@@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
+// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//
@@ -44,6 +45,8 @@ class SettingsContext {
fileprivate static let useOldBeacon = "GDASettingsUseOldBeacon"
fileprivate static let playBeaconStartEndMelody = "GDAPlayBeaconStartEndMelody"
fileprivate static let automaticCalloutsEnabled = "GDASettingsAutomaticCalloutsEnabled"
+ fileprivate static let calloutSoundEffectsEnabled = "GDASettingsCalloutSoundEffectsEnabled"
+ fileprivate static let calloutPausesEnabled = "GDASettingsCalloutPausesEnabled"
fileprivate static let shakeCalloutsEnabled = "GDASettingsShakeCalloutsEnabled"
fileprivate static let sensePlace = "GDASettingsPlaceSenseEnabled"
fileprivate static let senseLandmark = "GDASettingsLandmarkSenseEnabled"
@@ -99,6 +102,8 @@ class SettingsContext {
Keys.useOldBeacon: false,
Keys.playBeaconStartEndMelody: false,
Keys.automaticCalloutsEnabled: true,
+ Keys.calloutSoundEffectsEnabled: true,
+ Keys.calloutPausesEnabled: true,
Keys.shakeCalloutsEnabled: false,
Keys.sensePlace: true,
Keys.senseLandmark: true,
@@ -425,6 +430,24 @@ extension SettingsContext: AutoCalloutSettingsProvider {
NotificationCenter.default.post(name: .automaticCalloutsEnabledChanged, object: self, userInfo: [Keys.enabled: newValue])
}
}
+
+ var calloutSoundEffectsEnabled: Bool {
+ get {
+ return userDefaults.bool(forKey: Keys.calloutSoundEffectsEnabled)
+ }
+ set(newValue) {
+ userDefaults.set(newValue, forKey: Keys.calloutSoundEffectsEnabled)
+ }
+ }
+
+ var calloutPausesEnabled: Bool {
+ get {
+ return userDefaults.bool(forKey: Keys.calloutPausesEnabled)
+ }
+ set(newValue) {
+ userDefaults.set(newValue, forKey: Keys.calloutPausesEnabled)
+ }
+ }
var shakeCalloutsEnabled: Bool {
get {
diff --git a/apps/ios/GuideDogs/Code/Behaviors/Default/AutoCalloutGenerator.swift b/apps/ios/GuideDogs/Code/Behaviors/Default/AutoCalloutGenerator.swift
index f007d716e..116f1a54d 100644
--- a/apps/ios/GuideDogs/Code/Behaviors/Default/AutoCalloutGenerator.swift
+++ b/apps/ios/GuideDogs/Code/Behaviors/Default/AutoCalloutGenerator.swift
@@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
+// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//
@@ -118,7 +119,6 @@ class AutoCalloutGenerator: AutomaticGenerator, ManualGenerator {
// MARK: - Private Constants
private let inVehicleBeaconUpdateDistance: CLLocationDistance = 1000.0 // meters
- private let calloutDelay = 0.75
// MARK: - Private Properties
@@ -141,6 +141,10 @@ class AutoCalloutGenerator: AutomaticGenerator, ManualGenerator {
}
// MARK: - Helper Properties
+
+ private var calloutDelay: TimeInterval {
+ return settings.calloutPausesEnabled ? 0.75 : 0.0
+ }
private var calloutRangeContext: CalloutRangeContext {
return spatialData.motionActivityContext.isInVehicle ? .automotive : .standard
@@ -435,7 +439,7 @@ class AutoCalloutGenerator: AutomaticGenerator, ManualGenerator {
guard let dataView = spatialData.getDataView(for: location, searchDistance: context.searchDistance) else {
let callouts = filterAnnounceablePOIs(prioritizedPOIs, near: location, context: context) {
- return POICallout(.auto, poi: $0, location: location)
+ return POICallout(.auto, poi: $0, location: location, includePrefixSound: settings.calloutSoundEffectsEnabled)
}
if callouts.isEmpty {
@@ -452,11 +456,11 @@ class AutoCalloutGenerator: AutomaticGenerator, ManualGenerator {
// Get the POIs sorted by distance, and filtered for duplicate names
let prioritizedCallouts = filterAnnounceablePOIs(prioritizedPOIs, near: location, context: context) {
- return POICallout(.auto, poi: $0, location: location)
+ return POICallout(.auto, poi: $0, location: location, includePrefixSound: settings.calloutSoundEffectsEnabled)
}
let defaultCallouts = !settings.automaticCalloutsEnabled ? [] : filterAnnounceablePOIs(dataView.pois, near: location, context: context) {
- return POICallout(.auto, key: $0.key, location: location)
+ return POICallout(.auto, key: $0.key, location: location, includePrefixSound: settings.calloutSoundEffectsEnabled)
}
if defaultCallouts.count + prioritizedCallouts.count == 0 {
diff --git a/apps/ios/GuideDogs/Code/Behaviors/Default/BeaconCalloutGenerator.swift b/apps/ios/GuideDogs/Code/Behaviors/Default/BeaconCalloutGenerator.swift
index f381ddd27..3af479525 100644
--- a/apps/ios/GuideDogs/Code/Behaviors/Default/BeaconCalloutGenerator.swift
+++ b/apps/ios/GuideDogs/Code/Behaviors/Default/BeaconCalloutGenerator.swift
@@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
+// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//
@@ -62,7 +63,6 @@ class BeaconCalloutGenerator: AutomaticGenerator, ManualGenerator {
// MARK: - Private Constants
private let inVehicleBeaconUpdateDistance: CLLocationDistance = 1000.0 // meters
- private let calloutDelay: TimeInterval = 0.75
// MARK: - Private Properties
@@ -73,6 +73,10 @@ class BeaconCalloutGenerator: AutomaticGenerator, ManualGenerator {
private var userLocation: CLLocation?
private var destinationKey: String?
private var beaconUpdateFilter: MotionActivityUpdateFilter
+
+ private var calloutDelay: TimeInterval {
+ return settings.calloutPausesEnabled ? 0.75 : 0.0
+ }
// MARK: - Initialization
@@ -100,7 +104,8 @@ class BeaconCalloutGenerator: AutomaticGenerator, ManualGenerator {
func handle(event: UserInitiatedEvent, verbosity: Verbosity) -> HandledEventAction? {
switch event {
case let event as BeaconCalloutEvent:
- return .playCallouts(CalloutGroup([DestinationCallout(.auto, event.beaconId)], action: .interruptAndClear, logContext: event.logContext))
+ let callout = DestinationCallout(.auto, event.beaconId, false, settings.calloutSoundEffectsEnabled)
+ return .playCallouts(CalloutGroup([callout], action: .interruptAndClear, logContext: event.logContext))
case let event as BeaconChangedEvent:
guard settings.automaticCalloutsEnabled else {
@@ -302,7 +307,7 @@ class BeaconCalloutGenerator: AutomaticGenerator, ManualGenerator {
configureDestinationUpdates()
// Build the destination callout
- return [DestinationCallout(origin, key, causedAudioDisable)]
+ return [DestinationCallout(origin, key, causedAudioDisable, settings.calloutSoundEffectsEnabled)]
}
// MARK: - Helper Methods
diff --git a/apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/DestinationCallout.swift b/apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/DestinationCallout.swift
index aaf398f2b..5b2d0cd75 100644
--- a/apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/DestinationCallout.swift
+++ b/apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/DestinationCallout.swift
@@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
+// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//
@@ -23,7 +24,7 @@ struct DestinationCallout: POICalloutProtocol {
return "destination"
}
- let includePrefixSound = true
+ let includePrefixSound: Bool
var prefixSound: Sound? {
return GlyphSound(.startJourney)
@@ -52,10 +53,11 @@ struct DestinationCallout: POICalloutProtocol {
return marker?.getPOI()
}
- init(_ calloutOrigin: CalloutOrigin, _ entityKey: String, _ causedAudioDisabled: Bool = false) {
+ init(_ calloutOrigin: CalloutOrigin, _ entityKey: String, _ causedAudioDisabled: Bool = false, _ includePrefixSound: Bool = true) {
self.origin = calloutOrigin
self.entityKey = entityKey
self.causedAudioDisabled = causedAudioDisabled
+ self.includePrefixSound = includePrefixSound
}
func hasSameEntity(_ rhs: POICallout) -> Bool {
diff --git a/apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/IntersectionCallout.swift b/apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/IntersectionCallout.swift
index c2827f361..f1d112f5e 100644
--- a/apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/IntersectionCallout.swift
+++ b/apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/IntersectionCallout.swift
@@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
+// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//
@@ -25,7 +26,7 @@ struct IntersectionCallout: CalloutProtocol {
return "intersection"
}
- let includePrefixSound = true
+ let includePrefixSound: Bool
var prefixSound: Sound? {
return GlyphSound(SuperCategory.intersections.glyph)
@@ -38,11 +39,12 @@ struct IntersectionCallout: CalloutProtocol {
return SpatialDataCache.intersectionByKey(key)
}
- init(_ calloutOrigin: CalloutOrigin, _ intersectionKey: String, _ isRoundabout: Bool = false, _ userHeading: CLLocationDirection) {
+ init(_ calloutOrigin: CalloutOrigin, _ intersectionKey: String, _ isRoundabout: Bool = false, _ userHeading: CLLocationDirection, _ includePrefixSound: Bool = true) {
self.origin = calloutOrigin
self.key = intersectionKey
self.isRoundabout = isRoundabout
self.heading = userHeading
+ self.includePrefixSound = includePrefixSound
}
func sounds(for location: CLLocation?, isRepeat: Bool, automotive: Bool = false) -> Sounds {
diff --git a/apps/ios/GuideDogs/Code/Behaviors/Default/IntersectionGenerator.swift b/apps/ios/GuideDogs/Code/Behaviors/Default/IntersectionGenerator.swift
index bf36af9a1..336098fd8 100644
--- a/apps/ios/GuideDogs/Code/Behaviors/Default/IntersectionGenerator.swift
+++ b/apps/ios/GuideDogs/Code/Behaviors/Default/IntersectionGenerator.swift
@@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
+// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//
@@ -136,7 +137,11 @@ class IntersectionGenerator: AutomaticGenerator {
return .noAction
case let event as IntersectionArrivalEvent:
- let callout = IntersectionCallout(.intersection, event.key, event.isRoundabout, event.heading)
+ let callout = IntersectionCallout(.intersection,
+ event.key,
+ event.isRoundabout,
+ event.heading,
+ SettingsContext.shared.calloutSoundEffectsEnabled)
GDATelemetry.track("callout", with: ["context": "intersection.arrival",
"type": callout.logCategory,
@@ -146,7 +151,9 @@ class IntersectionGenerator: AutomaticGenerator {
return .playCallouts(CalloutGroup([callout], action: .interruptAndClear, logContext: "intersections"))
case let event as IntersectionDepartureEvent:
- let callout = event.geocodedLocation.buildCallout(origin: .intersection, sound: true, useClosestRoadIfAvailable: true)
+ let callout = event.geocodedLocation.buildCallout(origin: .intersection,
+ sound: SettingsContext.shared.calloutSoundEffectsEnabled,
+ useClosestRoadIfAvailable: true)
return .playCallouts(CalloutGroup([callout], action: .enqueue, logContext: "intersections"))
default:
diff --git a/apps/ios/GuideDogs/Code/Visual UI/Controls/Settings/CalloutSettingsCellView.swift b/apps/ios/GuideDogs/Code/Visual UI/Controls/Settings/CalloutSettingsCellView.swift
index 8b6d09251..3f865d6c4 100644
--- a/apps/ios/GuideDogs/Code/Visual UI/Controls/Settings/CalloutSettingsCellView.swift
+++ b/apps/ios/GuideDogs/Code/Visual UI/Controls/Settings/CalloutSettingsCellView.swift
@@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
+// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//
@@ -13,7 +14,7 @@ protocol CalloutSettingsCellViewDelegate: AnyObject {
}
internal enum CalloutSettingCellType {
- case all, poi, mobility, beacon, shake
+ case all, poi, mobility, beacon, shake, soundEffects, delays
}
class CalloutSettingsCellView: UITableViewCell {
@@ -44,6 +45,12 @@ class CalloutSettingsCellView: UITableViewCell {
return
case .shake:
settingSwitch.isOn = SettingsContext.shared.shakeCalloutsEnabled
+ return
+ case .soundEffects:
+ settingSwitch.isOn = SettingsContext.shared.calloutSoundEffectsEnabled
+ return
+ case .delays:
+ settingSwitch.isOn = SettingsContext.shared.calloutPausesEnabled
}
}
}
@@ -97,6 +104,14 @@ class CalloutSettingsCellView: UITableViewCell {
case .shake:
SettingsContext.shared.shakeCalloutsEnabled = isOn
GDATelemetry.track("settings.shake_callouts", value: isOn.description)
+ return
+ case .soundEffects:
+ SettingsContext.shared.calloutSoundEffectsEnabled = isOn
+ GDATelemetry.track("settings.callout_sound_effects", value: isOn.description)
+ return
+ case .delays:
+ SettingsContext.shared.calloutPausesEnabled = isOn
+ GDATelemetry.track("settings.callout_delay", value: isOn.description)
}
}
}
diff --git a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift
index 2839b8c08..4cb69d71b 100644
--- a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift
+++ b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift
@@ -30,10 +30,12 @@ class SettingsViewController: BaseTableViewController {
private enum CalloutsRow: Int, CaseIterable {
case all = 0
- case poi = 1
- case mobility = 2
- case beacon = 3
- case shake = 4
+ case soundEffects = 1
+ case delays = 2
+ case poi = 3
+ case mobility = 4
+ case beacon = 5
+ case shake = 6
}
private static let cellIdentifiers: [IndexPath: String] = [
@@ -51,6 +53,8 @@ class SettingsViewController: BaseTableViewController {
IndexPath(row: CalloutsRow.mobility.rawValue, section: Section.callouts.rawValue): "mobilityCallouts",
IndexPath(row: CalloutsRow.beacon.rawValue, section: Section.callouts.rawValue): "beaconCallouts",
IndexPath(row: CalloutsRow.shake.rawValue, section: Section.callouts.rawValue): "shakeCallouts",
+ IndexPath(row: CalloutsRow.soundEffects.rawValue, section: Section.callouts.rawValue): "soundCallouts",
+ IndexPath(row: CalloutsRow.delays.rawValue, section: Section.callouts.rawValue): "delayCallouts",
IndexPath(row: 0, section: Section.streetPreview.rawValue): "streetPreview",
IndexPath(row: 0, section: Section.troubleshooting.rawValue): "troubleshooting",
@@ -62,7 +66,9 @@ class SettingsViewController: BaseTableViewController {
IndexPath(row: CalloutsRow.poi.rawValue, section: Section.callouts.rawValue),
IndexPath(row: CalloutsRow.mobility.rawValue, section: Section.callouts.rawValue),
IndexPath(row: CalloutsRow.beacon.rawValue, section: Section.callouts.rawValue),
- IndexPath(row: CalloutsRow.shake.rawValue, section: Section.callouts.rawValue)
+ IndexPath(row: CalloutsRow.shake.rawValue, section: Section.callouts.rawValue),
+ IndexPath(row: CalloutsRow.soundEffects.rawValue, section: Section.callouts.rawValue),
+ IndexPath(row: CalloutsRow.delays.rawValue, section: Section.callouts.rawValue)
]
// MARK: Properties
@@ -91,7 +97,7 @@ class SettingsViewController: BaseTableViewController {
switch sectionType {
case .general: return 6
case .audio: return 1
- case .callouts: return SettingsContext.shared.automaticCalloutsEnabled ? 5 : 1
+ case .callouts: return SettingsContext.shared.automaticCalloutsEnabled ? 7 : 1
case .streetPreview: return 1
case .troubleshooting: return 1
case .about: return 1
@@ -118,6 +124,8 @@ class SettingsViewController: BaseTableViewController {
case .mobility: cell.type = .mobility
case .beacon: cell.type = .beacon
case .shake: cell.type = .shake
+ case .soundEffects: cell.type = .soundEffects
+ case .delays: cell.type = .delays
}
}
diff --git a/apps/ios/GuideDogs/Code/Visual UI/Views/Settings.storyboard b/apps/ios/GuideDogs/Code/Visual UI/Views/Settings.storyboard
index 0807f1425..1fb6a2069 100644
--- a/apps/ios/GuideDogs/Code/Visual UI/Views/Settings.storyboard
+++ b/apps/ios/GuideDogs/Code/Visual UI/Views/Settings.storyboard
@@ -405,6 +405,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+