mirror of
https://github.com/carey314/mio-plugin-worker.git
synced 2026-08-10 07:04:32 +00:00
feat(sound): swap sit/clockout tones for better character
- sit: Morse → Submarine (1.5s sonar). Longer sustain + lower
pitch pierces meeting audio and noise-cancelling
headphones better than Morse's crisp staccato. 1Hz
cadence over 15s still drives urgency.
- clockout: Glass → Hero (1.1s heroic fanfare). Glass was "✨
achievement"; Hero is "🎺 victory" — better scale for
the 下班 moment. Three plays with 0.6s gap.
Both still macOS-built-in (/System/Library/Sounds), zero external
deps. Single hard-coded path per sound — to swap again, edit the
URL line. Function name `playMorseSitAlert` kept for callsite
compat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5e5540d9b1
commit
8a66043f6f
@ -25,22 +25,29 @@ final class SoundPlayer {
|
|||||||
static let shared = SoundPlayer()
|
static let shared = SoundPlayer()
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
/// System Morse sound — 0.7s staccato 3-tone "S".
|
/// Sit-alert sound. Picked Submarine (1.5s sonar ping) over the
|
||||||
/// Shipped on every macOS since at least 10.0; safe to hard-code path.
|
/// original Morse — the sonar's longer sustain + lower pitch
|
||||||
private let morseURL = URL(fileURLWithPath: "/System/Library/Sounds/Morse.aiff")
|
/// pierces noise-cancelling headphones and meeting audio better,
|
||||||
|
/// while the 15-second 1Hz cadence still drives the urgency.
|
||||||
|
/// To switch back: change to "Morse" / "Tink" / "Ping" / etc.
|
||||||
|
/// All built-in: /System/Library/Sounds/*.aiff
|
||||||
|
private let sitAlertURL = URL(fileURLWithPath: "/System/Library/Sounds/Submarine.aiff")
|
||||||
|
|
||||||
private var timer: Timer?
|
private var timer: Timer?
|
||||||
private var remaining: Int = 0
|
private var remaining: Int = 0
|
||||||
private var currentSound: NSSound?
|
private var currentSound: NSSound?
|
||||||
|
|
||||||
/// Repeated Morse alert: play `count` times at 1Hz. Each play is a
|
/// Repeated sit alert: play `count` times at 1Hz. Each play is a
|
||||||
/// fresh NSSound so previous-in-flight playback doesn't get stomped
|
/// fresh NSSound so previous-in-flight playback doesn't get stomped
|
||||||
/// (NSSound.play() is non-blocking and the 0.7s tone overlaps with
|
/// (NSSound.play() is non-blocking; Submarine's 1.5s overlap with
|
||||||
/// the next-second silence — acceptable acoustic gap).
|
/// the next-second trigger creates a slightly layered effect —
|
||||||
|
/// acoustically more present than crisp single beeps).
|
||||||
///
|
///
|
||||||
/// Calling while a previous alert is still running cancels the old
|
/// Calling while a previous alert is still running cancels the old
|
||||||
/// one and restarts from `count`. This keeps "user sat back down +
|
/// one and restarts from `count`. This keeps "user sat back down +
|
||||||
/// trigger fired again 45min later" clean.
|
/// trigger fired again 45min later" clean.
|
||||||
|
///
|
||||||
|
/// Name retained for source compat (callsite is single, low cost).
|
||||||
func playMorseSitAlert(count: Int = 15) {
|
func playMorseSitAlert(count: Int = 15) {
|
||||||
stop()
|
stop()
|
||||||
remaining = count
|
remaining = count
|
||||||
@ -50,19 +57,21 @@ final class SoundPlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clockout celebration tone — Glass (1.0s sustained "✨" tone)
|
/// Clockout celebration tone — Hero (1.1s heroic fanfare),
|
||||||
/// played 3 times with 0.5s gap. Distinct from sit Morse so the
|
/// played 3 times with 0.6s gap. Picked over the original Glass
|
||||||
/// user instantly knows "下班" not "起来动一下".
|
/// for more triumph energy on the "下班" moment. Glass was
|
||||||
|
/// "✨ achievement"; Hero is "🎺 victory" — appropriate scale.
|
||||||
|
/// To switch: change to "Funk" / "Sosumi" / "Glass" / etc.
|
||||||
func playClockoutCelebration() {
|
func playClockoutCelebration() {
|
||||||
stop()
|
stop()
|
||||||
let glassURL = URL(fileURLWithPath: "/System/Library/Sounds/Glass.aiff")
|
let celebrationURL = URL(fileURLWithPath: "/System/Library/Sounds/Hero.aiff")
|
||||||
// 3 chimes at 0s, 0.6s, 1.2s — shorter cadence than sit (full
|
// 3 chimes at 0s, 0.6s, 1.2s — full shot of joy, not a nag.
|
||||||
// shot of joy, not a nag). Schedule with DispatchQueue async
|
// Schedule with DispatchQueue async since this is a fixed-shot
|
||||||
// since this is a fixed-shot pattern, no need for a Timer loop.
|
// pattern, no need for a Timer loop.
|
||||||
for delay in [0.0, 0.6, 1.2] {
|
for delay in [0.0, 0.6, 1.2] {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||||
guard let _ = self else { return }
|
guard let _ = self else { return }
|
||||||
if let s = NSSound(contentsOf: glassURL, byReference: true) {
|
if let s = NSSound(contentsOf: celebrationURL, byReference: true) {
|
||||||
s.play()
|
s.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,11 +97,11 @@ final class SoundPlayer {
|
|||||||
// byReference: true — load lazily from the system path each call.
|
// byReference: true — load lazily from the system path each call.
|
||||||
// The cost is negligible (< 1ms) and avoids keeping a 200KB AIFF
|
// The cost is negligible (< 1ms) and avoids keeping a 200KB AIFF
|
||||||
// resident across the 45min idle stretch between alerts.
|
// resident across the 45min idle stretch between alerts.
|
||||||
if let sound = NSSound(contentsOf: morseURL, byReference: true) {
|
if let sound = NSSound(contentsOf: sitAlertURL, byReference: true) {
|
||||||
sound.play()
|
sound.play()
|
||||||
currentSound = sound
|
currentSound = sound
|
||||||
} else {
|
} else {
|
||||||
WorkerDebugLog.write("SoundPlayer: failed to load \(morseURL.path)")
|
WorkerDebugLog.write("SoundPlayer: failed to load \(sitAlertURL.path)")
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user