Files
Find-Word/docs/AUDIO_PROVENANCE.md
2026-10-14 10:01:46 +00:00

17 KiB
Raw Permalink Blame History

Find-Word v0.2.3 오디오 출처와 재현

포함 자산과 생성 설정

대상 생성 방식 원문/설정 배포 방식
public/audio/intro.wav Microsoft Edge online TTS neural 음성 src/content.ts의 INTRO 24kHz mono PCM16 WAV
목표 음성 100개 같은 provider·voice 10 패턴 × 10 단어, renderSentence 같은 WAV
8개 배경 환경 공개 라이선스 실제 현장 녹음 public/ambience/manifest.json 12초·24kHz mono PCM16 WAV
화면 파형·S 캐릭터 직접 작성한 CSS App.tsx, styles.css CSS

voice는 ko-KR-HyunsuMultilingualNeural, rate는 -6%, pitch는 -4Hz, 생성 클라이언트는 edge-tts 7.3.1이다. 직접 작성한 한국어 문장을 온라인 provider에 보내 MP3 응답을 받은 뒤 FFmpeg로 PCM을 만든다. 음성 복제나 사람의 녹음 샘플을 사용하지 않는다. 웃음은 INTRO의 ‘으하하하~~’를 같은 voice로 합성한다. 앱은 저장한 WAV만 같은 출처에서 가져오며 런타임 외부 TTS/API, speechSynthesis, 인증정보가 필요하지 않다.

청취용 샘플은 intro.wav와 luck-apple.wav이다. 101개 전체 생성 결과의 크기·길이 범위와 독립 검증은 v0.1.1 RESULT에 기록한다. public/audio/manifest.json은 version, provider, voice, rate, pitch, sampleRate, 생성 시각, 실측 edge-tts/FFmpeg 버전, 파일별 원문·SHA-256·bytes·duration·RMS·peak를 담는다.

설치와 생성

생성 환경은 Linux (기존 디렉터리 교체에 renameat2(RENAME_EXCHANGE) 필요), Python 3.11 이상, FFmpeg, Node.js 22.22.2 이상이다. 이미 포함된 WAV를 빌드·실행할 때는 Python/FFmpeg/provider 연결이 필요 없다. 생성기 계약 테스트는 Python과 FFmpeg를 사용하되 네트워크 없이 동작한다.

python3 -m venv .venv-audio
.venv-audio/bin/python -m pip install -r requirements-audio.txt
.venv-audio/bin/python -m pip check
npm run audio:generate
npm test
npm run build

audio:generate는 .venv-audio/bin/python scripts/generate_audio.py로 고정되어 전역 Python으로 대체되지 않는다. requirements-audio.txt는 edge-tts==7.3.1과 전이 의존성 버전을 고정한다. 생성기는 실제 설치 버전을 조회하고 7.3.1이 아니면 외부 요청 전에 실패한다. .venv-audio/는 Git과 배포물에서 제외한다.

원문을 Node type stripping으로 앱 데이터에서 읽고, 101개 모두 같은 파일시스템의 public/.audio-stage-*/에 순차 생성한다. 각 provider 요청은 최대 60초, 최대 3회 시도이며 실패 사이 1초/2초를 기다린다. FFmpeg도 60초 제한이 있다. 재시도 전 불완전 MP3를 삭제하고 변환·검증 실패는 즉시 중단한다. 로그에는 파일명·시도 횟수만 남기고 provider stderr/응답·쿠키·인증 헤더를 저장하지 않는다.

전체 WAV와 manifest를 검증한 뒤 기존 public/audio와 완성 디렉터리를 단일 원자 교환으로 바꾼다. 실패·Ctrl-C는 기존 디렉터리의 바이트를 그대로 보존한다. 원자 교환을 지원하지 않는 파일시스템에서는 실패하며 비원자 방식으로 우회하지 않는다. 성공 후 이전 자산과 임시 MP3를 정리한다. 강제 종료(SIGKILL)·전원 손실 때는 숨김 staging 폴더가 남을 수 있으나 공개 디렉터리는 교환 전 또는 후의 전체 집합이다. 이는 전원 손실 시 저장장치 내구성 보장을 뜻하지 않는다.

고정 클라이언트와 설정으로 생성 절차를 재현할 수 있다. 원격 모델·서비스 버전은 고정할 수 없어 재생성 WAV의 바이트 동일성은 보장하지 않는다. 새 실행의 manifest를 검토해야 한다. 빌드는 이미 생성한 WAV의 해시를 보존하고 101개 모두 포함됐는지 확인한다.

레벨 처리와 검증

MP3를 24kHz mono signed 16-bit little-endian PCM으로 디코드한다. 파일 전체에 동일한 선형 gain을 적용해 RMS 0.10을 목표로 하며 증폭은 최대 2배, peak는 0.89로 제한한다. 동적 압축·노이즈 게이트·침묵 제거·시간 변형은 하지 않는다. 원래 신호의 자음과 억양을 보존하면서 파일 간 레벨 차이와 피크를 완화한다.

생성기와 독립 자산 테스트는 101개 파일의 원문·집합·SHA-256·bytes·duration, 24kHz mono PCM16, 1–20초 길이, RMS > 100/32768, full-scale 미도달, 총 48MiB 미만을 검사한다. 기존 음성 단독 검사는 RMS 0.03과 +6dB에서 peak < 0.8을 요구한다. 0.2.3의 사용자 최대 +12dB 혼합은 세 경로 공통 감쇠로 별도 검증한다. 앱은 Web Audio에서 배경·음성을 RMS 정규화한 뒤 상대 SNR을 적용한다. 물리적 음압이나 주파수별 청감을 보정한 값은 아니다.

도구 라이선스와 서비스 조건

  • edge-tts 7.3.1 LICENSE: 대부분의 코드에 LGPLv3, srt_composer.py에만 MIT가 적용된다. 공식 프로젝트는 Microsoft Edge의 온라인 TTS를 이용하는 클라이언트다. 클라이언트 라이선스가 provider 사용·음성 출력의 재배포 권한을 대신 부여하지 않는다. 이 프로젝트는 클라이언트/모델을 번들하지 않는다.
  • FFmpeg 공식 라이선스: 빌드 옵션에 따라 LGPL/GPL이 적용된다. 실제 사용 버전은 manifest에 남기며 실행 파일은 배포하지 않는다.
  • Microsoft Services Agreement의 AI Services 등 해당 서비스 조건은 도구의 오픈소스 라이선스와 별개다. 위 문서 §13(s)(vi)는 출력의 권리·사용 가능성을 이용자가 판단하도록 설명한다. API 키 없이 호출된다는 사실이 상업 사용·재배포의 포괄적 허락이나 SLA를 뜻하지 않는다. 배포자는 자신의 이용 상황에 적용되는 최신 조건을 확인해야 한다.
  • 프로젝트는 생성 음성 자체에 독점 권리를 주장하지 않는다. LICENSE의 소스·문서 권리 유보와 provider 및 제3자의 권리는 구분한다. 런타임 React, ReactDOM, scheduler의 MIT 전문은 THIRD_PARTY_NOTICES.md에 있다.

문서 확인일: 2026-10-14 UTC. provider의 음성 목록, rate/pitch 지원, 가용성, 접근 제한, 조건은 바뀔 수 있다. 온라인 생성이 중단되어도 포함된 WAV의 빌드·재생에는 영향이 없다. 실패 시 다른 voice로 자동 대체하지 않는다.

청감과 평가의 한계

자동 검증은 PCM 신호·무결성·재생 흐름을 확인한다. 한국어 발음, 유사 자모의 구분, 웃음의 자연스러움과 실제 사용자의 난이도는 청취 평가가 필요하다. 대표 샘플과 게임 내 소음 혼합 상태를 직접 들어 인수 판단한다. 배경은 실제 현장 녹음의 짧은 구간이다. 이 게임의 수치는 임상 검증·치료·청력 판정용이 아니다.

v0.2.3 현장 녹음 파생본

원본 선정 계약은 .agent/AMBIENCE-SOURCES-v0.2.3.json, 배포 바이트의 출처·직접 URL·저작자·라이선스·변환·SHA-256·bytes·duration·RMS·peak는 manifest, 정확한 attribution과 변경 고지는 AMBIENCE_NOTICES에 있다. 이 고지는 dist에도 포함되고 UI에서 링크한다. 여덟 원본 모두 서로 다른 현장 녹음이며 NC/ND 자산을 사용하지 않는다. 원본은 저장소/빌드에 포함하지 않는다.

환경 저작자 라이선스 원본 구간(초)
길거리 cori (Cori Samuel) PD-author 35–47
쇼핑몰 thore (Thore) PD-author 36–48
극장 ElevatorFan2020 CC0 1.0 45–57
백화점 icmusic CC0 1.0 90–102
시장 Marie Flutre CC BY-SA 4.0 24–36
지하철 PascalHD CC BY 4.0 65–77
카페 Marble Toast CC0 1.0 0–12
비 오는 거리 Extemporalist CC0 1.0 180–192

Commons primary 페이지/API와 Freesound primary 페이지의 라이선스 근거를 evidence에 보존한다. Freesound 두 원본은 지정된 preview MP3이다. 프로젝트의 비공개 라이선스를 제3자 녹음에 적용하지 않으며, 시장 파생 WAV는 CC BY-SA 4.0으로 계속 공개·수정·재배포할 수 있다. 지하철은 CC BY 4.0의 PascalHD credit·변경 고지·라이선스 링크를 유지한다.

재현: 검증한 원본 8개를 /tmp/findword-ambience-sources에 놓고 python3 scripts/ambience_audio.py를 실행한다. 계획의 파일별 selectedSegmentStartSeconds에서 12초를 선택하고, FFmpeg로 mono 24kHz float PCM 디코드 후 양끝 50ms 선형 fade, RMS 목표 .08, 최대 증폭 4배/peak .85 중 가장 작은 선형 gain을 적용한다. 마지막은 표준 44-byte WAV 헤더의 PCM16이다. 시간 변형·절차적 합성·원본 음성 대체는 없다. 원본 SHA-256과 실측 FFmpeg 버전을 함께 기록하므로 같은 원본/도구로 재현 가능하다.

런타임은 음성/배경 각각 RMS .03 후 상대 gain 10^(dB/20)을 적용한다. −12~+12 dB 범위에서도 세 경로의 최악 피크 합을 공통 감쇠하여 최대 .8로 제한한다. 양끝 fade가 있는 배경을 loop하고 목표 끝에서 정리한다. 전체 버퍼 RMS 기준이며 짧은 시간창별 SNR이나 실제 음압을 보증하지 않는다. 원본 녹음의 우연한 사람 말소리는 환경의 일부다. 자동 검사로 음악·개인 대화의 완전한 부재를 인증할 수 없으므로 사람의 최종 청취 검토가 필요하다.

기존 neural WAV 101개의 원문·해시는 v0.1.1 그대로다. 음성 manifest의 version은 배포 버전 0.2.3, generatedForVersion은 0.1.1이며 원래 생성 시각·도구 정보는 보존한다.

리뷰 후 출처 고정과 원자 발행

승인 계획의 expectedSourceSha256와 sourceBytes는 불변 입력이다. 생성기는 로컬 경로나 현재 파일에서 이 승인값을 새로 기록하지 않으며, 여덟 원본의 해시·크기를 첫 변환 전에 모두 검사한다. 한 원본이라도 다르거나 누락되면 실패한다. sourceVerification은 검증 상태·일자·방법·보존된 증거를 가리킨다. 임시 경로를 담던 verifiedLocalSource 필드는 제거했다.

directMediaDurationSeconds는 directUrl에서 받은 바로 그 바이트의 ffprobe format.duration이다. Freesound 항목은 preview MP3의 길이이며 primary 페이지 원본 길이 또는 반올림한 표시 길이가 아니다. 배포 manifest의 duration과 conversion.durationSeconds는 파생 WAV의 길이(12초), selectedSegmentStartSeconds는 파일별 불변 승인 시작점이며 생성기는 별도 hardcoded map 없이 이 값을 읽는다. conversion.startSeconds는 같은 값이어야 한다. 시작점은 유한한 0 이상 수이고, 시작점+12초가 directMediaDurationSeconds 이하여야 한다. 누락·음수·NaN·Infinity·문자열·bool·범위 초과는 첫 변환 전에 거부한다. 원본 전체 디코드와 실제 수치는 보존된 source-integrity.json에 있다.

생성기는 출력 폴더와 같은 부모의 .ambience-stage-*/ready에서 8 WAV·manifest·AMBIENCE_NOTICES.md를 완성한다. 정확한 파일 집합, 고유 해시, 원본 계획, PCM 형식·길이·fade·RMS·peak, 항목별 저작자/primary URL/direct URL/라이선스 링크/outputLicense/변경 설명을 다시 검사한다. 존재하는 symlink 및 dangling symlink 출력을 모두 거부한다. Linux renameat2(RENAME_EXCHANGE) 한 번이 교체의 commit 지점이며, 최초 생성은 같은 파일시스템의 rename 한 번이다. 지원하지 않는 환경에서는 실패한다. 파일별 overlay를 하지 않으므로 오래된 파일이 남지 않고 고지도 음원과 같은 디렉터리로 교체된다.

commit 전 변환·쓰기·검증·교환 실패 또는 중단은 이전 디렉터리를 보존한다. syscall 이후에는 완성된 새 디렉터리가 공개된 상태다. SIGKILL 등 강제 종료는 숨은 임시 디렉터리를 남길 수 있지만 부분 배포는 만들지 않는다. 전원 손실에 대한 디스크 내구성이나 여러 HTTP 요청에 걸친 snapshot 격리를 주장하지 않는다. 앞선 독립 리뷰에서는 기존 109 WAV를 재생성하지 않았다. 이후 아래 privacy refinement에서 전체 현장음 bundle을 원자 재생성했으며 mall/market/cafe 3개만 바이트가 바뀌었다. 사람의 현장음 청취 인수는 미실행이다.

최종 privacy excerpt refinement

현재 선택은 mall 36–48초, market 24–36초, cafe 0–12초다. Neo가 동일한 scripts/ambience_audio.convert 경로와 아래 ASR 설정으로 해시 고정 원본을 재검사해 제시한 market/cafe 후보를 사용했다. 제공받은 재검사 설명은 selection-context.json에 별도로 기록했으며, 원본 후보 결과를 최종 WAV 검사 결과로 대신하지 않았다.

승인 원본 8개의 SHA-256·bytes·ffprobe 길이를 먼저 대조하고 하드닝된 생성기로 8 WAV·manifest·notice 전체를 staging/검증/원자 교환했다. 생성 실측 기록에 현재 해시·RMS·peak·변환이 있다. 이번 재시도는 market/cafe 두 WAV만 변경했다. 101개 voice, 다섯 unaffected ambience, 기존 mall 36초 WAV는 모두 바이트 동일하다.

일부 Ogg 구간은 FFmpeg timestamp/seek 때문에 12초보다 몇 프레임 짧게 출력된다. 짧은 결과일 때만 처음부터 디코드한 24kHz PCM의 round(start × 24000)부터 정확한 288,000개 sample을 선택하고 출력 PTS를 sample 수로 재설정한다. silent padding이나 time stretching은 없다. 정말 원본이 짧으면 여전히 실패하며 conversion.seekMethod가 실제 경로를 기록한다.

final-asr.json은 정확한 현재 8 WAV를 로컬 faster-whisper 1.2.1, tiny multilingual, CPU int8, beam_size=5, best_of=5, 기본 Silero VAD 활성화, condition_on_previous_text=true로 검사한 원문이다. 모델 revision d90ca5fe260221311c53c58e660288d3deb8d356, 모델/VAD 파일 해시, 라이브러리 버전·전체 설정·각 입력 해시·반환 segment/info를 저장했다. 이전 실패 실행과 모든 decoder/VAD 인자가 같은지 스크립트가 대조한다. mall/market/cafe 각각 segment 0개, duration_after_vad=0.0, requiredEmptyPassed=true다. 다른 다섯 WAV도 segment 0개다. threshold 조절이나 반환 segment 삭제는 하지 않았다. 테스트는 지정 시작점·true gate·VAD 설정·실제 WAV/manifest 해시와 아래 false 인수값을 함께 강제한다.

humanListeningAcceptance=false, legalPrivacyClearance=false를 유지한다. Tiny-model ASR은 screening signal일 뿐이며 빈 결과는 말소리·음악·개인 대화가 없음을 증명하지 않는다. 출력 text/language도 환각일 수 있고 충실한 전사나 화자 식별로 주장하지 않는다. 독립적인 사람 청취 인증은 수행하지 않았다. 다만 제품 소유자 Oz는 2026-10-14 제공된 최종 WAV 8개 청취 검수 묶음과 명시된 자동 검사 한계를 기준으로 현재 자산 그대로 릴리스를 진행하도록 승인했다. 이 승인은 제3자의 법적/privacy clearance를 대신하지 않는다.

이전 market 60–72초/cafe 180–192초 실행은 market 3개/cafe 1개 segment를 반환하여 requiredEmptyPassed=false였으며 release acceptance 실패였다. 그 실행의 모든 screening 파일은 읽기 전용 prior-attempt-market60-cafe180 아래 바이트 그대로 보존했다. 당시 final-asr.json을 성공으로 덮어쓰지 않았고 회귀 테스트가 이전 실패 상태와 모든 보존 파일 해시를 검사한다.

기존 no-VAD 증거도 바이트 그대로 남겼다. 이 결과는 mall 36초/market 60초/cafe 180초에서 환각일 수 있는 candidate text를 냈으며, 새 market/cafe WAV의 검사 결과가 아니다. no-VAD 결과는 지정된 VAD gate를 대체하거나 뒤집지 않는다. 두 방법 모두 말소리/음악의 부재를 증명하지 않으며 사람의 청취 인수나 법적/privacy clearance를 제공하지 않는다. Neo의 이전 후보 scan, 이전 파생본 ASR, 당시 provenance도 변경하지 않았다. raw JSON에 없던 설정이나 입력 연결을 사후에 있었던 것으로 주장하지 않는다.

새 RED → GREEN, 전체 검증과 보존 기록은 retry-market24-cafe0, 현재 검증 요약은 RESULT의 마지막 절에 있다.