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
18 changes: 10 additions & 8 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<img width="1024" height="818" alt="SoundVisualizer" src="https://github.com/user-attachments/assets/b11aa5b3-c995-4e36-8ff6-3e2f2c2b2388" />

<img width="1024" alt="SoundVisualizer 오버레이: 환경음, 대화음, 위협음, 왼쪽에서 나는 소리, 오른쪽에서 나는 소리" src="docs/images/readme_screens.png" />
<img width="1024" alt="SoundVisualizer 오버레이: 환경음, 사람 소리, 위협음, 왼쪽에서 나는 소리, 오른쪽에서 나는 소리" src="docs/images/readme_screens.png" />

[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![WPF](https://img.shields.io/badge/WPF-blue?style=for-the-badge&logo=windows)](https://docs.microsoft.com/en-us/dotnet/desktop/wpf/)
Expand All @@ -23,9 +23,9 @@
<br/>

> **SoundVisualizer**는 컴퓨터의 시스템 오디오를 실시간으로 캡처하여 방향·크기·의미를 그래픽 오버레이로 변환하는 데스크톱 애플리케이션입니다.
단순한 파형 시각화를 넘어, **YAMNet**과 **ONNX Runtime** 기반의 AI 사운드 분류기를 내장하여 현재 재생 중인 소리의 종류(배경음, 대화음, 위협음 등)를 실시간으로 인지하고 피드백을 제공합니다.
단순한 파형 시각화를 넘어, **YAMNet**과 **ONNX Runtime** 기반의 AI 사운드 분류기를 내장하여 현재 재생 중인 소리의 종류(환경음, 사람 소리, 위협음)를 실시간으로 인지하고 피드백을 제공합니다.

> 📱 안드로이드 버전도 있습니다: [SoundVisualizer for Android](https://github.com/bibibic76/SoundVisualizer)
> 📱 안드로이드 버전도 있습니다: [SoundVisualizer for Android](https://github.com/SOVIS-Team/SoundVisualizer-Android)

---

Expand Down Expand Up @@ -59,6 +59,7 @@
- **패드 모드 (Pad)**: 지정된 공간 그리드(방향)에 고정된 패드 형태로 소리를 표현합니다.
- **외곽선 모드 (Outline)**: 파도 모드를 기반으로, 파도의 상한 외곽선 테두리만 표현하여 화면 가림을 최소화합니다.
- 30 FPS부터 모니터 주사율까지, 최대 프레임을 얼마로 두어도 그래픽이 같은 빠르기로 움직입니다.
- **쉬운 설정**: 설정 탭은 그림으로 선택하는 화면으로 열립니다. 모양, 크기(작게·보통·크게), 보여 줄 소리 종류를 선택하고, 나머지 설정은 ‘자세한 설정’에서 바꿉니다.

### 2. 온스크린 실시간 제어 및 에디터
전체 화면 게임을 내리지 않고도 오버레이 상태에서 즉각적인 수정이 가능합니다.
Expand All @@ -71,14 +72,15 @@
- 스테레오 출력에서는 좌우에 함께 있는 소리를 화면 위쪽에, 좌우 차이만 옆쪽에 그립니다. 그래서 가운데에서 나는 소리가 양쪽에서 나는 소리처럼 보이지 않습니다.

### 4. AI 실시간 사운드 분류 (ONNX & YAMNet)
- **3-Class 분류 체계**: 모든 소리를 `Ambient(환경음)`, `Speech(대화음)`, `Danger(위협음)` 3가지로 분석하여 카테고리별로 지정된 UI 색상과 투명도로 동적 시각 피드백을 제공합니다. 색상과 투명도는 커스터마이징이 가능합니다.
- **3-Class 분류 체계**: 모든 소리를 `Ambient(환경음)`, `Human sounds(사람 소리)`, `Danger(위협음)` 3가지로 분석하여 카테고리별로 지정된 UI 색상과 투명도로 동적 시각 피드백을 제공합니다. 색상과 투명도는 커스터마이징이 가능합니다.
- **위협 단서**: 사이렌, 경보, 폭발음, 폭죽 같은 경고음은 모델의 상위 후보 다섯 개 안에(5% 이상) 들기만 해도 `위협음`으로 봅니다. 음악이나 게임 소리에 묻혀도 놓치지 않기 위해서입니다. 총소리 이름은 오경보를 막기 위해 이렇게 끌어올리지 않습니다.
- **흔들리지 않는 색**: `위협음`은 조건을 넘는 첫 결과에 바로 바뀌고, `대화음`과 `환경음`은 두 번 연달아 같아야 바뀌어 색이 깜빡이지 않습니다. 조용한 동안에는 AI 가 쉬고, 소리가 나면 바로 다시 듣습니다.
- **흔들리지 않는 색**: `위협음`은 조건을 넘는 첫 결과에 바로 바뀌고, `사람 소리`와 `환경음`은 두 번 연달아 같아야 바뀌어 색이 깜빡이지 않습니다. 조용한 동안에는 AI 가 쉬고, 소리가 나면 바로 다시 듣습니다.
- **더 빠른 사람 소리**: 말소리가 시작되면 작은 음성 감지 모델(Silero VAD)이 새로 들어온 소리를 확인해 `사람 소리`를 한 박자(약 0.25초) 먼저 보여 줍니다. 화면 표시만 바꾸며, `위협음`과 분류 탭에서 정한 종류가 늘 우선합니다.
- **모든 채널을 듣습니다**: 5.1/7.1 환경에서는 앞쪽뿐 아니라 뒤쪽·옆쪽 채널의 소리도 AI 가 듣습니다.
- **늦게 온 위협음**: `위협음` 판정이 소리보다 조금 늦게 와도, 그 소리를 나던 때의 크기로 그립니다. 앞서 숨겨 둔 종류였던 소리도 그렇습니다.

### 5. 소리마다 종류 정하기 (분류 탭)
- AI 가 알아듣는 소리 521개를 안드로이드 버전과 같은 묶음으로 보여 주고, 묶음마다(고급 모드에서는 소리마다) `환경음`, `대화음`, `위협음` 가운데 무엇으로 볼지 선택합니다.
- AI 가 알아듣는 소리 521개를 안드로이드 버전과 같은 묶음으로 보여 주고, 묶음마다(고급 모드에서는 소리마다) `환경음`, `사람 소리`, `위협음` 가운데 무엇으로 볼지 선택합니다.
- 이름으로 찾고, 종류나 바꾼 소리로 거르고, 한 번에 모두 되돌릴 수 있습니다. 바꾼 것은 오버레이의 AI 에 바로 반영됩니다.

### 6. 도움말, 안내, 트레이 아이콘
Expand Down Expand Up @@ -111,7 +113,7 @@

SoundVisualizer는 설치가 필요 없는 가벼운 **포터블(Portable)** 패키지로 제공됩니다. Windows 10/11 (64비트)에서 돌아가며, .NET 을 따로 설치하지 않아도 됩니다.

1. **[GitHub Releases](https://github.com/amophi/SoundVisualizer/releases)** 페이지에 접속합니다.
1. **[GitHub Releases](https://github.com/SOVIS-Team/SoundVisualizer-Windows/releases)** 페이지에 접속합니다.
2. 최신 버전의 `SoundVisualizer_vX.Y.zip` 파일을 다운로드해 원하는 곳에 압축 해제합니다.
3. 폴더 내의 **`SoundVisualizer.exe`**를 실행합니다.
4. 런처에서 언어 및 초기 설정을 마친 후, **실행**을 눌러 오버레이를 켭니다.
Expand All @@ -127,7 +129,7 @@ SoundVisualizer는 설치가 필요 없는 가벼운 **포터블(Portable)** 패
1. **요구 사항**: Windows 10/11, .NET 10 SDK. Visual Studio 2026 (.NET 데스크톱 개발 워크로드)은 있어도 되고 없어도 됩니다.
2. **리포지토리 클론**:
```bash
git clone https://github.com/amophi/SoundVisualizer.git
git clone https://github.com/SOVIS-Team/SoundVisualizer-Windows.git
```
3. 리포지토리 폴더에서 빌드하고 테스트합니다.
```bash
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<img width="1024" height="818" alt="SoundVisualizer" src="https://github.com/user-attachments/assets/b11aa5b3-c995-4e36-8ff6-3e2f2c2b2388" />

<img width="1024" alt="SoundVisualizer overlay: ambient, speech, danger, sound from the left, and sound from the right" src="docs/images/readme_screens.png" />
<img width="1024" alt="SoundVisualizer overlay: ambient, human sounds, danger, sound from the left, and sound from the right" src="docs/images/readme_screens.png" />

[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![WPF](https://img.shields.io/badge/WPF-blue?style=for-the-badge&logo=windows)](https://docs.microsoft.com/en-us/dotnet/desktop/wpf/)
Expand All @@ -23,9 +23,9 @@
<br/>

> **SoundVisualizer** is a desktop application that captures real-time system audio and translates its direction, intensity, and meaning into a graphical overlay.
Going beyond simple waveform rendering, it integrates an AI sound classifier powered by **YAMNet** and **ONNX Runtime** to instantly identify the type of sound playing (ambient, speech, danger) and provide adaptive visual feedback.
Going beyond simple waveform rendering, it integrates an AI sound classifier powered by **YAMNet** and **ONNX Runtime** to instantly identify the type of sound playing (ambient, human sounds, danger) and provide adaptive visual feedback.

> 📱 There is also an Android version: [SoundVisualizer for Android](https://github.com/bibibic76/SoundVisualizer).
> 📱 There is also an Android version: [SoundVisualizer for Android](https://github.com/SOVIS-Team/SoundVisualizer-Android).

---

Expand Down Expand Up @@ -59,6 +59,7 @@ We provide 4 unique rendering modes that intuitively map sound intensity and dir
- **Pad Mode**: Displays glowing pads anchored to specific spatial grid directions.
- **Outline Mode**: A minimalist variation of Wave mode, showing only the thin borders of the wave to minimize screen occlusion.
- Animations move at the same speed at any frame rate, from 30 FPS up to your monitor's refresh rate.
- **Easy settings**: The Settings tab opens with a picture-based view where you choose the shape, the size (Small, Medium, Large), and which sound types to show. Switch to “Detailed” for every other setting.

### 2. On-Screen Real-Time Editor (F4)
Modify settings instantly while in a full-screen application or game without minimizing the window.
Expand All @@ -71,14 +72,15 @@ Modify settings instantly while in a full-screen application or game without min
- With stereo output, sound common to both channels is drawn at the top and only the difference between left and right goes to the sides, so a centred sound no longer looks like sound coming from both sides.

### 4. Real-Time AI Sound Classification (ONNX & YAMNet)
- **3-Class Detection**: Analyzes all incoming audio into `Ambient`, `Speech`, and `Danger` categories. Each category triggers independent, customizable UI colors and dynamic opacity changes.
- **3-Class Detection**: Analyzes all incoming audio into `Ambient`, `Human sounds`, and `Danger` categories. Each category triggers independent, customizable UI colors and dynamic opacity changes.
- **Warning cues**: Sirens, alarms, explosions, fireworks and similar warning sounds count as `Danger` even when they are only among the model's top five candidates (at 5% or more), so music or game audio can't drown them out. Gunshot labels are not promoted this way, to avoid false alarms.
- **Stable colors**: `Danger` appears on the first qualifying result; `Speech` and `Ambient` need two in a row, so the color doesn't flicker. The AI pauses while it's quiet and resumes as soon as sound returns.
- **Stable colors**: `Danger` appears on the first qualifying result; `Human sounds` and `Ambient` need two in a row, so the color doesn't flicker. The AI pauses while it's quiet and resumes as soon as sound returns.
- **Faster human sounds**: When a voice starts, a small voice detector (Silero VAD) checks the newest audio and can show `Human sounds` one step (about 0.25 s) earlier. It only changes what you see: `Danger` and your choices in the Sounds tab always win.
- **Every channel counts**: On 5.1/7.1 systems, the AI hears the rear and side channels too, not just the front.
- **Late danger**: If a `Danger` result arrives a moment after the sound, the overlay still draws that sound at the size it had, even when its earlier type was hidden.

### 5. Choose How Each Sound Counts (Sounds Tab)
- Browse the 521 sounds the AI knows, grouped like the Android version, and choose whether each group (or, in advanced mode, each sound) counts as `Ambient`, `Speech`, or `Danger`.
- Browse the 521 sounds the AI knows, grouped like the Android version, and choose whether each group (or, in advanced mode, each sound) counts as `Ambient`, `Human sounds`, or `Danger`.
- Search by name, filter by type or by the sounds you changed, and reset everything in one step. The overlay's AI follows your changes immediately.

### 6. Help, Notices, and Tray Icon
Expand Down Expand Up @@ -111,7 +113,7 @@ The project is rigorously engineered for high-performance, real-time background

SoundVisualizer is provided as a lightweight, **portable (no-install)** open-source package for Windows 10/11 (64-bit). You don't need to install .NET.

1. Go to the **[GitHub Releases](https://github.com/amophi/SoundVisualizer/releases)** page.
1. Go to the **[GitHub Releases](https://github.com/SOVIS-Team/SoundVisualizer-Windows/releases)** page.
2. Download the latest `SoundVisualizer_vX.Y.zip` file and extract it to any location on your PC.
3. Run **`SoundVisualizer.exe`**.
4. Configure your initial settings and language in the launcher, then click **Start** to activate the overlay.
Expand All @@ -127,7 +129,7 @@ To build from source to modify the code or contribute to the project:
1. **Prerequisites**: Windows 10/11 and the .NET 10 SDK. Visual Studio 2026 (with the .NET Desktop Development workload) is optional.
2. **Clone the repository**:
```bash
git clone https://github.com/amophi/SoundVisualizer.git
git clone https://github.com/SOVIS-Team/SoundVisualizer-Windows.git
```
3. Build and test from the repository folder:
```bash
Expand Down
42 changes: 42 additions & 0 deletions SoundVisualizer.Core/Ai/AiAudioBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public sealed class AiAudioBuffer
private readonly int _windowSamples;
private int _tail;
private int _count;
// 말소리 미리보기(SileroStreamWorker)가 링의 어디까지 읽었는지 맞춰 보는 값. 프레임은 이 세대에 들어온 모노 프레임 수이고,
// 세대는 Reset 마다 하나씩 올라 이전 캡처의 소리를 새 캡처의 소리로 읽지 않게 한다(Android #422).
private long _totalFrames;
private long _generation;

/// <summary>섞은 모노를 링에 넣기 전에 잠깐 두는 곳. 버퍼마다 할당하지 않으려고 다시 쓴다.</summary>
private readonly float[] _monoScratch = new float[MaxRingSize / 2];
Expand Down Expand Up @@ -73,6 +77,43 @@ public void Reset()
{
_tail = 0;
_count = 0;
_totalFrames = 0;
_generation++;
}
}

/// <summary>링의 세대와 이 세대에 들어온 모노 프레임 수. 어느 스레드에서나 부른다.</summary>
internal readonly record struct StreamCursor(long Generation, long Frames);

/// <summary>지금의 세대와 들어온 프레임 수(Android 의 cursor()).</summary>
internal StreamCursor Cursor()
{
lock (_lock) return new StreamCursor(_generation, _totalFrames);
}

/// <summary>
/// 이 세대의 절대 프레임 [<paramref name="first"/>, <paramref name="first"/> + <paramref name="destination"/>.Length) 를 복사한다
/// (Android 의 copyRange). 0 보다 앞의 프레임은 0 이다. 세대가 다르거나, 아직 다 들어오지 않았거나, 이미 2초 밖으로 밀려났으면
/// 복사하지 않고 false 를 돌려준다. 그래서 미래의 소리나 덮어쓴 소리, 이전 캡처의 소리를 새 소리로 읽지 않는다.
/// 모델을 기다리지 않고 잠금 안에서 복사만 한다.
/// </summary>
internal bool CopyRange(long generation, long first, Span<float> destination)
{
lock (_lock)
{
long size = destination.Length;
if (generation != _generation || first + size > _totalFrames || Math.Max(first, 0L) < _totalFrames - _count) return false;

int leading = (int)Math.Clamp(-first, 0L, size);
destination[..leading].Clear();
int remaining = destination.Length - leading;
if (remaining == 0) return true;
// 링 끝(_tail)이 프레임 _totalFrames 다. 2의 거듭제곱 마스크라 음수도 링 안의 자리로 돈다.
int ringStart = (int)((_tail - (_totalFrames - (first + leading))) & _ringMask);
int firstPart = Math.Min(remaining, _ring.Length - ringStart);
_ring.AsSpan(ringStart, firstPart).CopyTo(destination[leading..]);
_ring.AsSpan(0, remaining - firstPart).CopyTo(destination[(leading + firstPart)..]);
return true;
}
}

Expand Down Expand Up @@ -108,6 +149,7 @@ private void AppendMono(ReadOnlySpan<float> monoChunk)
{
lock (_lock)
{
_totalFrames += monoChunk.Length;
for (int i = 0; i < monoChunk.Length; i++)
{
_ring[_tail] = monoChunk[i];
Expand Down
28 changes: 28 additions & 0 deletions SoundVisualizer.Core/Ai/AiClassificationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,33 @@ public sealed class AiClassificationResult
/// (<see cref="AiClassificationStage.LastClassification"/>).
/// </summary>
internal FrozenDictionary<string, string> MappingInputs { get; init; } = FrozenDictionary<string, string>.Empty;

/// <summary>
/// 말소리 미리보기로 한 박자 먼저 보이는 화면 전용 결과인지(Android #422). 확정 결과(<see cref="RealtimeAiPipeline.LastClassification"/>)는
/// 늘 false 이고, 화면용 <see cref="RealtimeAiPipeline.LastDisplayClassification"/> 만 true 인 결과를 줄 수 있다.
/// </summary>
public bool SpeechPreview { get; init; }

/// <summary>
/// 이 확정 결과(환경음)를 부모로 한 미리보기(Android 의 parent.copy(coarse = "speech", ..., speechPreview = true)). 종류는 대화음,
/// 이름과 확신도는 부모 프레임의 대화음 후보 값, 시각은 미리보기를 만든 때다. 상위 5개·소요시간·매핑은 부모 그대로 둔다.
/// </summary>
internal AiClassificationResult AsSpeechPreview(string display, float confidence, long timestampMs) => new()
{
Coarse = SoundType.Speech,
Display = display,
Confidence = confidence,
Top5 = Top5,
DangerCuePromoted = DangerCuePromoted,
MeetsThreshold = MeetsThreshold,
TimestampMs = timestampMs,
PreprocessMs = PreprocessMs,
YamnetMs = YamnetMs,
TotalMs = TotalMs,
MappingOverrideCount = MappingOverrideCount,
MappingSignature = MappingSignature,
MappingInputs = MappingInputs,
SpeechPreview = true,
};
}
}
Loading
Loading