File size: 4,377 Bytes
df41178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
audio.py -- 音のようすを日本語で言う

  「何を言ったか」ではなく「どんな音か」。
    強弱   … 大きさ(デシベル)
    高さ   … 声や音の高さ(Hz)
    音色   … 明るいか、こもっているか(スペクトル重心)
    ざらつき… 息や雑音の多さ(ゼロ交差率)
    区切り … 鳴っているところ/静かなところ

  【実測】同じ文を、別の声でしゃべらせたとき
      Kyoko   256.4 Hz  明るさ 2188   (高くて明るい)
      Eddy    107.0 Hz  明るさ 1362
      Grandma  98.9 Hz  明るさ 1138
      Grandpa  84.8 Hz  明るさ  723   (低くてこもっている)
    ちゃんと別々の数字になる。

  使うのは macOS の AVFoundation と Accelerate だけ。ネットには出ない。
"""
import json, os, subprocess

HERE = os.path.dirname(os.path.abspath(__file__))
HEAR = os.path.join(HERE, "tools", "hear")

EXT = {".aiff", ".aif", ".wav", ".m4a", ".mp3", ".caf", ".aac", ".flac"}


def ready():
    return os.path.exists(HEAR)


def hear(path, timeout=120):
    if not ready():
        raise Exception("音を聞く道具がありません(tools/hear)")
    path = os.path.expanduser(path)
    if not os.path.exists(path):
        raise Exception(f"その音のファイルがありません: {path}")
    r = subprocess.run([HEAR, path], capture_output=True, text=True,
                       timeout=timeout)
    if r.returncode != 0:
        raise Exception((r.stderr or "聞けませんでした").strip()[:200])
    return json.loads(r.stdout)


# ---- 数字を、ことばに直す ----
def _loud(db):
    if db > -12: return "とても大きい"
    if db > -20: return "大きい"
    if db > -30: return "ふつう"
    if db > -45: return "小さい"
    return "とても小さい"


def _pitch(hz):
    if hz <= 0: return "高さがはっきりしない(音楽でない音や、雑音)"
    if hz < 100: return f"とても低い({hz:.0f} Hz) 男性の低い声くらい"
    if hz < 160: return f"低い({hz:.0f} Hz) 男性の声くらい"
    if hz < 260: return f"中くらい({hz:.0f} Hz) 女性の声くらい"
    if hz < 400: return f"高い({hz:.0f} Hz)"
    return f"とても高い({hz:.0f} Hz)"


def _tone(c):
    if c <= 0: return "音色が測れない"
    if c < 900:  return f"こもった、丸い音(重心 {c:.0f} Hz)"
    if c < 1800: return f"落ちついた音(重心 {c:.0f} Hz)"
    if c < 3200: return f"明るい音(重心 {c:.0f} Hz)"
    return f"とても明るい・鋭い音(重心 {c:.0f} Hz)"


def _rough(z):
    if z < 0.05: return "なめらか(きれいな音)"
    if z < 0.12: return "すこしざらつく"
    if z < 0.25: return "ざらつきが多い(息や雑音まじり)"
    return "とてもざらつく(雑音が主)"


def describe(path):
    d = hear(path)
    g = d["全体"]
    segs = d["鳴っているところ"]
    rate = len(segs) / d["長さ秒"] if d["長さ秒"] else 0

    out = [f"{os.path.basename(path)}"
           f"({d['長さ秒']} 秒 / {d['サンプリング周波数']:,} Hz / "
           f"{d['音の道']} つの音の道)",
           f"  強弱  : {_loud(g['デシベル'])}"
           f"({g['デシベル']} dB、山は {g['いちばん大きいところ']} dB)",
           f"  高さ  : {_pitch(g['高さ中央値'])}",
           f"  音色  : {_tone(g['明るさ中央値'])}",
           f"  ざらつき: {_rough(g['ざらつき'])}({g['ざらつき']})",
           f"  鳴っているところ: {len(segs)} か所"]
    for s in segs[:8]:
        out.append(f"    {s['始まり']:.2f} 〜 {s['おわり']:.2f} 秒")
    if len(segs) > 8:
        out.append(f"    …ほか {len(segs)-8} か所")
    if rate > 1.2:
        out.append("  → 切れ目が多いので、話し声か、たたく音かもしれません")
    elif len(segs) == 1 and d["長さ秒"] < 3:
        out.append("  → ひと続きの短い音。合図の音などでしょう")
    return "\n".join(out)


if __name__ == "__main__":
    import sys
    for p in sys.argv[1:] or []:
        print(describe(p)); print()
    if not sys.argv[1:]:
        print("  使い方: python3 audio.py <音のファイル>")