9月3日に Lyria 3.5 がパブリックプレビューへ入ったという知らせを読んだのは、ヒーリング音源のアプリで次のアップデートに載せる候補を並べ直している最中でした。フルレングスの楽曲生成に対応し、尺と構成を細かく制御でき、44.1kHz ステレオで出てくる——そこまで読んで、手が止まりました。
止まった理由は、期待よりも不安のほうでした。そのときフォルダを見ると、自分で選んだ音と、試しに生成してみた音が、同じ階層に並んでいたのです。頭に draft_ と付けてはいましたが、線引きと呼べるものはそれだけでした。
最初にお伝えしたいのは、これがモデルの性能をどう評価するかという話ではないということです。生成の質が上がるほど、どこまでを配信物に載せるかという自分の判断を、気分ではなく仕組みで守る必要が出てきます。その仕組みを、私は四十数行のスクリプトに落とすことにしました。
名前の頭文字は、コピーの一回で消えます
最初のうち、私は接頭辞で足りると思っておりました。生成した音には draft_、自分で選んだ音にはそのままの名前。目で見て分かるのだから十分だ、という判断です。
結果は芳しくありませんでした。書き出しツールを通したとき、別のマシンへ持っていったとき、一括リネームで通し番号を振り直したとき——そのたびに接頭辞は落ちます。落ちたことに気づけるのは、次に自分がそのファイルを開いたときだけです。
実際、候補フォルダには calm_13_new.wav という名前のファイルが残っていました。いま思えば、これは「あとで決める」と思って置いたまま忘れたものでした。名前を見ても、自分の手で選んだ音なのか、生成して比較用に置いた音なのか、判断がつきません。
ファイル名も、置き場所も、更新日時も、人間の作業ひとつで書き換わります。書き換わらないものを主キーにしない限り、線引きは持ちません。
Lyria 3.5 で変わったところ(2026年9月3日・パブリックプレビュー)
事実の側を先に整理しておきます。
| 項目 | 内容 |
|---|---|
| 提供状況 | パブリックプレビュー(2026年9月3日) |
| 生成の尺 | フルレングスの楽曲生成に対応 |
| 改善点 | 音楽的な一貫性、自然なボーカル、尺と構成の細かい制御 |
| 入力 | テキストと画像 |
| 出力 | 44.1kHz ステレオの高忠実度音声 |
私にとっていちばん効いたのは、音質の項目ではなく「一度に丸ごと出てくる」ことのほうでした。30秒のクリップであれば、聴いた瞬間に試作だと分かります。ところが数分の完成形が並ぶと、候補と見分けがつかなくなるのです。
見分けがつかないものを、目視の運用で仕分けし続けることはできません。ここが、フォルダの置き方を変えると決めた地点でした。
中身のハッシュだけが、コピーを越えて残ります
そこで台帳の主キーを、ファイル名から中身の SHA-256 へ移しました。名前を変えても、フォルダを移しても、更新日時が変わっても、中身が同じである限りハッシュは変わりません。逆に、書き出し設定を変えて作り直せばハッシュも変わりますから、「作り直したのに登録し忘れている」状態も同じ仕組みで検出できます。
台帳は1行1音源のタブ区切りにしました。列は三つだけです。
| 列 | 値 | 意味 |
|---|---|---|
| 1列目 | SHA-256 | 中身の指紋。これが主キー |
| 2列目 | origin | hand(自分で選んだ)/ generated(生成物)/ reference(参考用) |
| 3列目 | note | 人が読むためのメモ。元のファイル名など |
そして、配信フォルダに置いてよいのは hand だけ、と決めました。
手で選んだ音だけが配信に乗り、生成した音は下見の棚に残ります。 この一行を、頭の中ではなくスクリプトの定数に書いたところから、迷いが減りました。
四十数行の台帳ゲート
書いたものをそのまま載せます。標準ライブラリだけで動きます。
#!/usr/bin/env python3
"""配信フォルダに入ってよい音源だけを通す台帳ゲート。"""
import hashlib
import pathlib
import sys
AUDIO_SUFFIXES = {".wav", ".mp3", ".m4a", ".aac", ".flac", ".ogg", ".caf"}
SHIPPABLE = {"hand"}
def digest(path: pathlib.Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
def load_ledger(path: pathlib.Path) -> dict:
ledger = {}
for lineno, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
line = raw.strip()
if not line or line.startswith("#"):
continue
parts = line.split("\t")
if len(parts) < 3:
sys.exit(f"台帳{lineno}行目の列が足りません: {raw!r}")
ledger[parts[0]] = (parts[1], parts[2])
return ledger
def main(ship_dir: str, ledger_file: str) -> int:
ledger = load_ledger(pathlib.Path(ledger_file))
unregistered, blocked, passed = [], [], 0
for path in sorted(pathlib.Path(ship_dir).rglob("*")):
if not path.is_file() or path.suffix.lower() not in AUDIO_SUFFIXES:
continue
entry = ledger.get(digest(path))
if entry is None:
unregistered.append(path)
elif entry[0] not in SHIPPABLE:
blocked.append((path, entry[0]))
else:
passed += 1
for path in unregistered:
print(f"未登録: {path}")
for path, origin in blocked:
print(f"配信不可({origin}): {path}")
print(f"通過 {passed} / 未登録 {len(unregistered)} / 配信不可 {len(blocked)}")
return 1 if unregistered or blocked else 0
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.exit("usage: audit.py <配信フォルダ> <台帳TSV>")
sys.exit(main(sys.argv[1], sys.argv[2]))なぜこう書いたのかを、三つだけ補足させてください。
ひとつめは、SHIPPABLE を集合にしてある点です。いまは hand だけですが、いずれ購入した音源を licensed として足す日が来ます。そのときに条件式を書き換えるのではなく、集合に一語足すだけで済むようにしました。判断の基準を一箇所に集めておくと、あとから読む自分が助かります。
ふたつめは、未登録と配信不可を分けて数えている点です。どちらも同じ「止まった」ですが、打つ手が違います。未登録は台帳に載せ忘れた作業の抜けで、確認して登録すれば進みます。配信不可は判断の誤りで、そのファイルを配信フォルダから外すまで進めてはいけません。まとめて件数だけ出すと、この区別が消えてしまうのです。
みっつめは、1 MiB ずつ読み進めている点です。数分のフルレングス音源はそれなりの容量になりますから、全部をメモリに載せる書き方は避けました。
手元で走らせて出た数字
まず、冒頭で触れた候補フォルダに近い構成——音源19本のうち17本が登録済み、という状態で走らせました。
未登録: ship/bgm/calm_13_new.wav
配信不可(generated): ship/bgm/draft_lyria_take3.wav
通過 17 / 未登録 1 / 配信不可 1
終了コードは 1 です。名前だけでは見分けがつかなくなっていた2本が、別々の理由で止まりました。私が欲しかったのは、まさにこの2行でした。
速度も測っておきました。30秒・44.1kHz ステレオの音源40本、合計 203 MB のフォルダに対して、3回とも 0.50 秒です。ハッシュは中身を最後まで読みますから、時間は本数ではなく総容量にほぼ比例します。数 GB のライブラリでも、書き出し直前に一度走らせる程度なら待たされる長さにはなりません。
置き場所は、配信用の書き出しを始める直前にしています。書き出したあとに気づくと、成果物を作り直すところからやり直しになるためです。
音の中身そのものの検査は、この台帳ゲートとは別の工程に分けています。ループの継ぎ目のように、聴かなくても数値で先に切り分けられるものについては、ループ音源のプチノイズは、Gemini に聴かせる前に波形の端で切り分けられますで扱いました。出所を確かめる工程と、品質を確かめる工程は、混ぜないほうが両方とも短く保てます。
締め出すのではなく、行き先を決める
ここまで読んでくださった方に、誤解のないようお伝えしておきたいことがあります。私は生成した音を使わないと決めたのではありません。配信物の内側と外側を分けただけです。
いま生成物を置いている先は三つあります。ひとつは画面のプロトタイプで、尺と構成を決めるための仮の音として当てています。ふたつめは、自分で音を組み立てるときの叩き台で、こういう流れもあるという確認に使います。みっつめは、既存のトラックが単調になっていないかを見直すときの比較対象です。いずれも、ユーザーの手元へは届かない場所です。
Gemini 側のモデルは、この数週間だけでも世代が動いています。単価が据え置きでも請求の形が変わることについてはGemini 3.8 Flash は単価が据え置きでも、請求が増えることがありますに書きました。モデルが変わるたびに判断をやり直さずに済むよう、線引きのほうを固定しておくと、更新の速さに振り回されにくくなります。
今日のうちに何かひとつだけ試すとしたら、配信フォルダを一度走査して、名前ではなくハッシュで台帳に載せ直すところまでで十分だと思います。私もそこから始めました。読んでくださってありがとうございました。