게시판SGF 기보로 학습하는 바둑 LLM 인공지능예제 소스

예제 소스

SGF 기보로 학습하는 바둑 LLM 인공지능 · 파일 169개

generate_stone_sound.py

#!/usr/bin/env python3
"""실제 바둑돌 소리 생성 - 나무와 돌이 부딪치는 소리.

사용법:
    python scripts/generate_stone_sound.py
"""

import numpy as np
import subprocess
import sys
from pathlib import Path


def generate_wood_stone_click(duration_ms: int = 15, sample_rate: int = 44100) -> bytes:
    """바둑돌 소리 - 짧고 건조한 '딱' 클릭음."""
    duration_s = duration_ms / 1000.0
    samples = int(sample_rate * duration_s)
    
    # 매우 짧은 임펄스 (충격음)
    # 대역 제한된 노이즈 (200-800Hz) - 나무 + 돌의 충격
    t = np.linspace(0, duration_s, samples, False)
    
    # 짧은 버스트 노이즈
    noise = np.random.normal(0, 0.7, samples)
    
    # 밴드패스 필터 효과 (간단한 방법)
    # 200Hz와 600Hz 혼합
    tone1 = 0.3 * np.sin(2 * np.pi * 200 * t)
    tone2 = 0.2 * np.sin(2 * np.pi * 600 * t)
    
    sound = noise + tone1 + tone2
    
    # 매우 급격한 감쇠 (공명 없는 건조한 소리)
    envelope = np.exp(-30 * t / duration_s)  # 극도로 빠른 감쇠
    
    sound = sound * envelope
    
    # 정규화
    sound = sound / np.max(np.abs(sound))
    
    # 16-bit PCM 변환
    sound_int = (sound * 32767).astype(np.int16)
    
    return sound_int.tobytes()


def create_stone_sounds():
    """흑돌/백돌/잡기 소리 생성."""
    try:
        import numpy as np
    except ImportError:
        print("numpy가 설치되지 않았습니다.")
        print("설치: pip install numpy")
        return False
    
    sounds_dir = Path("sounds")
    sounds_dir.mkdir(exist_ok=True)
    
    # 흑돌 (낮은 톤)
    print("🪨 흑돌 소리 생성 중...")
    black_sound = generate_wood_stone_click(150, 44100)
    
    # 백돌 (약간 높은 톤)
    print("⚪ 백돌 소리 생성 중...")
    # 주파수를 약간 높여서
    white_sound = generate_wood_stone_click(140, 44100)
    
    # 잡기 (더 크고 긴 소리)
    print("💥 잡기 소리 생성 중...")
    capture_sound = generate_wood_stone_click(250, 44100)
    
    # WAV 파일 생성 (간단한 헤더 + 데이터)
    def save_wav(filename: Path, data: bytes, sample_rate: int = 44100):
        """간단한 WAV 파일 저장."""
        import struct
        
        # WAV 헤더
        channels = 1
        bits_per_sample = 16
        byte_rate = sample_rate * channels * bits_per_sample // 8
        block_align = channels * bits_per_sample // 8
        data_size = len(data)
        
        with open(filename, 'wb') as f:
            # RIFF 헤더
            f.write(b'RIFF')
            f.write(struct.pack('<I', 36 + data_size))
            f.write(b'WAVE')
            
            # fmt 청크
            f.write(b'fmt ')
            f.write(struct.pack('<I', 16))  # 청크 크기
            f.write(struct.pack('<H', 1))   # PCM
            f.write(struct.pack('<H', channels))
            f.write(struct.pack('<I', sample_rate))
            f.write(struct.pack('<I', byte_rate))
            f.write(struct.pack('<H', block_align))
            f.write(struct.pack('<H', bits_per_sample))
            
            # data 청크
            f.write(b'data')
            f.write(struct.pack('<I', data_size))
            f.write(data)
    
    save_wav(sounds_dir / "stone_black.wav", black_sound)
    save_wav(sounds_dir / "stone_white.wav", white_sound)
    save_wav(sounds_dir / "stone_capture.wav", capture_sound)
    
    print(f"\n✅ 소리 파일 생성 완료: {sounds_dir}/")
    print("   - stone_black.wav")
    print("   - stone_white.wav")  
    print("   - stone_capture.wav")
    
    return True


def main() -> int:
    if not create_stone_sounds():
        return 1
    
    # 테스트 재생
    print("\n🔊 소리 테스트 중...")
    sounds_dir = Path("sounds")
    
    for sound_file in ["stone_black.wav", "stone_white.wav", "stone_capture.wav"]:
        path = sounds_dir / sound_file
        if path.exists():
            print(f"   재생: {sound_file}")
            subprocess.run(["afplay", str(path)], check=False)
    
    return 0


if __name__ == "__main__":
    sys.exit(main())