Skip to content

Commit

Permalink
fix: simpleaudio problem
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkShawn2020 committed Dec 17, 2024
1 parent 42695ca commit 1921fa7
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 140 deletions.
2 changes: 1 addition & 1 deletion .windsurfrules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
本项目基于:
- poetry
- poetry,如果要新增包,请直接 `poetry add xx`,否则需要先执行 `poetry lock`,再执行 `poetry install`
- pyqt
5 changes: 3 additions & 2 deletions docs/development/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ WatchCat 使用 PyInstaller 进行打包,通过 GitHub Actions 自动构建各
# 安装项目依赖
poetry install --with dev

# 使用 PyInstaller 打包
# 使用 PyInstaller 打包(使用配置文件)
poetry run pyinstaller watchcat.spec
```

打包后的文件将在 `dist/WatchCat` 目录下生成。在 macOS 系统上,你可以通过以下两种方式运行打包后的程序
打包后的文件将在 `dist/WatchCat` 目录下生成。在 macOS 系统上,你可以通过以下方式运行打包后的程序

1. 命令行方式:
```bash
Expand Down
44 changes: 0 additions & 44 deletions main.spec

This file was deleted.

4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ pyobjc-framework-cocoa = { version = "^10.3.2", platform = "darwin" }
pyobjc-framework-quartz = { version = "^10.3.2", platform = "darwin" }
pyobjc-framework-applicationservices = { version = "^10.3.2", platform = "darwin" }
pyobjc-framework-coretext = { version = "^10.3.2", platform = "darwin" }
simpleaudio = { version = "^1.0.4", platform = "darwin" }
sounddevice = { version = "^0.4.6", platform = "darwin" }
pyobjus = { version = "^1.2.1", platform = "darwin" }
rumps = { version = "^0.4.0", platform = "darwin" }

[tool.poetry.group.test.dependencies]
pytest = "^7.4.0"
Expand Down
35 changes: 22 additions & 13 deletions transparent_overlay/ImageMatchThread.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from math import fabs
import os
import platform
import subprocess
import time

import cv2
import numpy as np
from PyQt6.QtCore import QThread, pyqtSignal
import platform
import subprocess
import os

from notifypy import Notify

from PyQt6.QtCore import QThread, pyqtSignal
from PyQt6.QtWidgets import QSystemTrayIcon
from PyQt6.QtGui import QIcon
from transparent_overlay.log import logger
from transparent_overlay.sounds import SoundPlayer, SoundType

Expand All @@ -24,6 +25,10 @@ def __init__(self, sct, config):
self.running = False
self.last_match = None # 存储上次匹配位置
self.last_match_status = False # 跟踪上一次的匹配状态
# 初始化系统托盘图标用于通知
self.tray_icon = QSystemTrayIcon()
self.tray_icon.setIcon(QIcon()) # 空图标
self.tray_icon.show()

def set_target(self, image):
"""设置目标图片"""
Expand All @@ -36,13 +41,17 @@ def on_match(self, title, message):
"""跨平台发送系统通知和提示音"""
# 检查是否启用通知
if self.config.data.enable_notification:
# 使用 notify-py 发送通知(支持 Windows、Linux、macOS)
logger.info(f"发送通知: {title} - {message}")
notification = Notify()
notification.title = title
notification.message = message
notification.application_name = "Watch Cat"
notification.send(block=False) # 改为非阻塞,避免声音延迟
try:
# 使用 QSystemTrayIcon 发送通知
self.tray_icon.showMessage(
title,
message,
QSystemTrayIcon.MessageIcon.Information,
3000 # 显示3秒
)
logger.info(f"发送通知: {title} - {message}")
except Exception as e:
logger.warning(f"通知发送失败: {e}")

# 检查是否启用声音
if self.config.data.enable_sound:
Expand Down
119 changes: 59 additions & 60 deletions transparent_overlay/sounds.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""声音管理模块"""
import enum
import numpy as np
import simpleaudio as sa
import sounddevice as sd
from pydub import AudioSegment
import os
import time


class SoundType(enum.Enum):
Expand Down Expand Up @@ -33,8 +34,17 @@ def generate_sine_wave(frequency: float, duration: float, sample_rate: int = 441
numpy.ndarray: 音频数据
"""
t = np.linspace(0, duration, int(duration * sample_rate), False)
note = np.sin(2 * np.pi * frequency * t) * 32767
return note.astype(np.int16)
note = np.sin(2 * np.pi * frequency * t)
return note.astype(np.float32)

@classmethod
def _play_buffer(cls, audio_data, sample_rate=44100):
"""安全地播放音频缓冲区"""
try:
sd.play(audio_data, sample_rate, blocking=False)
except Exception as e:
from transparent_overlay.log import logger
logger.warning(f"播放音频失败: {e}")

@classmethod
def play_sound(cls, sound_type: SoundType, config=None) -> None:
Expand All @@ -57,6 +67,49 @@ def play_sound(cls, sound_type: SoundType, config=None) -> None:
elif sound_type == SoundType.CUSTOM and config:
cls.play_custom(config)

@classmethod
def play_beep(cls, frequency: float = 440, duration: float = 0.25) -> None:
"""播放蜂鸣声
Args:
frequency: 频率 (Hz),默认 440Hz (标准 A 音)
duration: 持续时间 (秒),默认 0.25 秒
"""
audio = cls.generate_sine_wave(frequency, duration)
cls._play_buffer(audio)

@classmethod
def play_success(cls) -> None:
"""播放成功提示音 (上升音)"""
audio1 = cls.generate_sine_wave(440, 0.1) # A4
audio2 = cls.generate_sine_wave(523.25, 0.1) # C5
audio = np.concatenate([audio1, audio2])
cls._play_buffer(audio)

@classmethod
def play_error(cls) -> None:
"""播放错误提示音 (下降音)"""
audio1 = cls.generate_sine_wave(440, 0.1) # A4
audio2 = cls.generate_sine_wave(349.23, 0.1) # F4
audio = np.concatenate([audio1, audio2])
cls._play_buffer(audio)

@classmethod
def play_mario(cls) -> None:
"""播放马里奥风格的提示音"""
frequencies = [660, 660, 0, 660, 0, 520, 660, 0, 784]
durations = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.15]
audio_parts = []

for freq, dur in zip(frequencies, durations):
if freq == 0: # 静音
audio_parts.append(np.zeros(int(dur * 44100), dtype=np.float32))
else:
audio_parts.append(cls.generate_sine_wave(freq, dur))

audio = np.concatenate(audio_parts)
cls._play_buffer(audio)

@classmethod
def play_custom(cls, config) -> None:
"""播放自定义音乐
Expand Down Expand Up @@ -88,70 +141,16 @@ def play_custom(cls, config) -> None:

# 导出为临时文件并播放
segment = segment.set_channels(1) # 转换为单声道
samples = np.array(segment.get_array_of_samples())
samples = np.array(segment.get_array_of_samples(), dtype=np.float32)

# 确保音量适中
max_sample = np.max(np.abs(samples))
if max_sample > 0:
scale = min(32767 / max_sample, 1.0)
samples = (samples * scale).astype(np.int16)
samples = samples / max_sample

# 播放音频
play_obj = sa.play_buffer(
samples,
num_channels=1,
bytes_per_sample=2,
sample_rate=segment.frame_rate
)
play_obj.stop_on_destroy = True
cls._play_buffer(samples, segment.frame_rate)

except Exception as e:
from transparent_overlay.log import logger
logger.warning(f"播放自定义音乐失败: {e}")

@classmethod
def play_beep(cls, frequency: float = 440, duration: float = 0.25) -> None:
"""播放蜂鸣声
Args:
frequency: 频率 (Hz),默认 440Hz (标准 A 音)
duration: 持续时间 (秒),默认 0.25 秒
"""
audio = cls.generate_sine_wave(frequency, duration)
play_obj = sa.play_buffer(audio, 1, 2, 44100)
play_obj.stop_on_destroy = True # 非阻塞播放

@classmethod
def play_success(cls) -> None:
"""播放成功提示音 (上升音)"""
audio1 = cls.generate_sine_wave(440, 0.1) # A4
audio2 = cls.generate_sine_wave(523.25, 0.1) # C5
audio = np.concatenate([audio1, audio2])
play_obj = sa.play_buffer(audio, 1, 2, 44100)
play_obj.stop_on_destroy = True

@classmethod
def play_error(cls) -> None:
"""播放错误提示音 (下降音)"""
audio1 = cls.generate_sine_wave(440, 0.1) # A4
audio2 = cls.generate_sine_wave(349.23, 0.1) # F4
audio = np.concatenate([audio1, audio2])
play_obj = sa.play_buffer(audio, 1, 2, 44100)
play_obj.stop_on_destroy = True

@classmethod
def play_mario(cls) -> None:
"""播放马里奥风格的提示音"""
frequencies = [660, 660, 0, 660, 0, 520, 660, 0, 784]
durations = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.15]
audio_parts = []

for freq, dur in zip(frequencies, durations):
if freq == 0: # 静音
audio_parts.append(np.zeros(int(dur * 44100), dtype=np.int16))
else:
audio_parts.append(cls.generate_sine_wave(freq, dur))

audio = np.concatenate(audio_parts)
play_obj = sa.play_buffer(audio, 1, 2, 44100)
play_obj.stop_on_destroy = True
33 changes: 14 additions & 19 deletions watchcat.spec
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ a = Analysis(
datas=[
('default_config.json', '.'),
('resources', 'resources'),
('venv/lib/python3.12/site-packages/notifypy/os_notifiers/binaries/Notificator.app', 'notifypy/os_notifiers/binaries'),
('*/site-packages/notifypy/os_notifiers/binaries/Notificator.app', 'notifypy/os_notifiers/binaries'),
],
hiddenimports=[
'PyQt6.QtCore',
Expand All @@ -26,40 +26,35 @@ a = Analysis(
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
excludes=['tkinter', 'matplotlib', 'PIL'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
noarchive=True,
collect_submodules=['PyQt6.QtCore', 'PyQt6.QtGui'],
collect_data_files=[('PyQt6', '.')],
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
exclude_binaries=True,
name='WatchCat',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
strip=False,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=True,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='WatchCat',
)

0 comments on commit 1921fa7

Please sign in to comment.