曲が会話するシリーズ
曲をサンプリング・切り貼りしてしゃべらせるMADのジャンル。ニコニコ黎明期から現代に至るまで、形を変えてしつこくこすられてるネタ。
これをNLPを使って自動化できたら面白そうだと思ったので取り組みました。
方法
Step 1 : 歌詞コーパスの作成
サンプリング元の曲の歌詞に対して、spacy + ja_ginzaを使ってtokenize、各tokenについて
- 各tokenの読みの取得
- 各tokenをモーラ (音の拍)に分割
を計算、結果をDBに保存しコーパスとして検索可能にする
Step 2 : 入力文と歌詞コーパスをマッチング
しゃべらせたいテキストを Step 1と同様にspacy + ja_ginzaでtokenize・解析する。
見つかったtokenを先頭からループし、以下の優先度でStep1で作った歌詞コーパス内のtokenとマッチングする。
- tokenの単語完全一致
- tokenの読み一致
- 複数モーラを組み合わせることで一致
- 例えば、「ミント」という単語は「シュールストレミング」「なっとう」というtokenのモーラ組み合わせで再現できる
今回作ったプログラム
以下レポジトリにおいてます。
結果
例 : 「米津玄師 Prazma」 X 「うさぎとかめ」
もしもし亀よ 亀さんよ
世界のうちに お前ほど ...
http://www3.u-toyama.ac.jp/niho/song/usagitokame/usagitokame.html
これを米津玄師の Prazmaの歌詞でマッチングした結果は以下のとおり
| マッチした歌詞の単語・モーラ (リンクはyoutubeのタイムスタンプ付きリンク) |
元の歌詞 |
|---|---|
| モ | もしもあの改札の... |
| シ | もしもあの改札の... |
| モ | もしもあの改札の... |
| シ | もしもあの改札の... |
| カ | ...あの改札の... |
| メ | 眩暈がした... |
| ヨ | 靴の汚れに... |
| カ | ...あの改札の... |
| メ | 眩暈がした... |
| サ | もしもあの改札の... |
| ン | あの裏門を越え... |
| ヨ | 靴の汚れに... |
| セカイ | 刹那 世界が... |
| ノ | もしもあの改札の... |
| ウ | あの裏門を越え... |
| チ | 立ち止まらず歩いて |
| ニ | 靴の汚れに... |
| ... |
各tokenについて、どのような単語・読み・モーラが歌詞のどの部分からとられたのかを表示できるCLIも作った
Input Text: もしもし亀よ 亀さんよ 世界のうちに お前ほど 歩みののろい ものはない
Timestamp: 2025-12-24 01:15:28.667064
Matches Found: 18
Summary:
Reconstructed Surface: 世界のにのはない
Reconstructed Reading:
モシモシカメヨカメサンヨセカイノウチニオマエホドノノロイモノハナイ
Stats: exact_surface=6, exact_reading=0, mora_combination=11
Match 1: MatchType.MORA_COMBINATION
├── Input
│ └── もしもし (モシモシ)
├── Matched Lyrics Tokens
│ └── もし (モシ) │ lemma: もし │ pos: ADV
└── Mora Trace
├── Mora: モ → Token: corpus_5d49c4d62cec_0_0 (index: 0)
├── Mora: シ → Token: corpus_5d49c4d62cec_0_0 (index: 1)
├── Mora: モ → Token: corpus_5d49c4d62cec_0_0 (index: 0)
└── Mora: シ → Token: corpus_5d49c4d62cec_0_0 (index: 1)
Match 2: MatchType.MORA_COMBINATION
├── Input
│ └── 亀 (カメ)
├── Matched Lyrics Tokens
│ ├── 改札 (カイサツ) │ lemma: 改札 │ pos: NOUN
│ └── 眩暈 (メマイ) │ lemma: 眩暈 │ pos: NOUN
└── Mora Trace
├── Mora: カ → Token: corpus_5d49c4d62cec_0_3 (index: 0)
└── Mora: メ → Token: corpus_5d49c4d62cec_0_77 (index: 0)
...
解析結果全文
Input Text: もしもし亀よ 亀さんよ 世界のうちに お前ほど 歩みののろい ものはない
Timestamp: 2025-12-24 01:15:28.667064
Matches Found: 18
Summary:
Reconstructed Surface: 世界のにのはない
Reconstructed Reading:
モシモシカメヨカメサンヨセカイノウチニオマエホドノノロイモノハナイ
Stats: exact_surface=6, exact_reading=0, mora_combination=11
Match 1: MatchType.MORA_COMBINATION
├── Input
│ └── もしもし (モシモシ)
├── Matched Lyrics Tokens
│ └── もし (モシ) │ lemma: もし │ pos: ADV
└── Mora Trace
├── Mora: モ → Token: corpus_5d49c4d62cec_0_0 (index: 0)
├── Mora: シ → Token: corpus_5d49c4d62cec_0_0 (index: 1)
├── Mora: モ → Token: corpus_5d49c4d62cec_0_0 (index: 0)
└── Mora: シ → Token: corpus_5d49c4d62cec_0_0 (index: 1)
Match 2: MatchType.MORA_COMBINATION
├── Input
│ └── 亀 (カメ)
├── Matched Lyrics Tokens
│ ├── 改札 (カイサツ) │ lemma: 改札 │ pos: NOUN
│ └── 眩暈 (メマイ) │ lemma: 眩暈 │ pos: NOUN
└── Mora Trace
├── Mora: カ → Token: corpus_5d49c4d62cec_0_3 (index: 0)
└── Mora: メ → Token: corpus_5d49c4d62cec_0_77 (index: 0)
Match 3: MatchType.MORA_COMBINATION
├── Input
│ └── よ (ヨ)
├── Matched Lyrics Tokens
│ └── 汚れ (ヨゴレ) │ lemma: 汚れ │ pos: NOUN
└── Mora Trace
└── Mora: ヨ → Token: corpus_5d49c4d62cec_0_51 (index: 0)
Match 4: MatchType.MORA_COMBINATION
├── Input
│ └── 亀 (カメ)
├── Matched Lyrics Tokens
│ ├── 改札 (カイサツ) │ lemma: 改札 │ pos: NOUN
│ └── 眩暈 (メマイ) │ lemma: 眩暈 │ pos: NOUN
└── Mora Trace
├── Mora: カ → Token: corpus_5d49c4d62cec_0_3 (index: 0)
└── Mora: メ → Token: corpus_5d49c4d62cec_0_77 (index: 0)
Match 5: MatchType.MORA_COMBINATION
├── Input
│ └── さん (サン)
├── Matched Lyrics Tokens
│ ├── 改札 (カイサツ) │ lemma: 改札 │ pos: NOUN
│ └── 裏門 (ウラモン) │ lemma: 裏門 │ pos: NOUN
└── Mora Trace
├── Mora: サ → Token: corpus_5d49c4d62cec_0_3 (index: 2)
└── Mora: ン → Token: corpus_5d49c4d62cec_0_32 (index: 3)
Match 6: MatchType.MORA_COMBINATION
├── Input
│ └── よ (ヨ)
├── Matched Lyrics Tokens
│ └── 汚れ (ヨゴレ) │ lemma: 汚れ │ pos: NOUN
└── Mora Trace
└── Mora: ヨ → Token: corpus_5d49c4d62cec_0_51 (index: 0)
Match 7: MatchType.EXACT_SURFACE
├── Input
│ └── 世界 (セカイ)
└── Matched Lyrics Tokens
└── 世界 (セカイ) │ lemma: 世界 │ pos: NOUN
Match 8: MatchType.EXACT_SURFACE
├── Input
│ └── の (ノ)
└── Matched Lyrics Tokens
└── の (ノ) │ lemma: の │ pos: ADP
Match 9: MatchType.MORA_COMBINATION
├── Input
│ └── うち (ウチ)
├── Matched Lyrics Tokens
│ ├── だろう (ダロウ) │ lemma: だ │ pos: AUX
│ └── 立ち止まら (タチドマラ) │ lemma: 立ち止まる │ pos: VERB
└── Mora Trace
├── Mora: ウ → Token: corpus_5d49c4d62cec_0_27 (index: 2)
└── Mora: チ → Token: corpus_5d49c4d62cec_0_7 (index: 1)
Match 10: MatchType.EXACT_SURFACE
├── Input
│ └── に (ニ)
└── Matched Lyrics Tokens
└── に (ニ) │ lemma: だ │ pos: AUX
Match 11: MatchType.MORA_COMBINATION
├── Input
│ └── お前 (オマエ)
├── Matched Lyrics Tokens
│ ├── 顔 (カオ) │ lemma: 顔 │ pos: NOUN
│ └── 前 (マエ) │ lemma: 前 │ pos: NOUN
└── Mora Trace
├── Mora: オ → Token: corpus_5d49c4d62cec_0_15 (index: 1)
├── Mora: マ → Token: corpus_5d49c4d62cec_0_5 (index: 0)
└── Mora: エ → Token: corpus_5d49c4d62cec_0_5 (index: 1)
Match 12: MatchType.MORA_COMBINATION
├── Input
│ └── ほど (ホド)
├── Matched Lyrics Tokens
│ ├── 星 (ホシ) │ lemma: 星 │ pos: NOUN
│ └── 立ち止まら (タチドマラ) │ lemma: 立ち止まる │ pos: VERB
└── Mora Trace
├── Mora: ホ → Token: corpus_5d49c4d62cec_0_45 (index: 0)
└── Mora: ド → Token: corpus_5d49c4d62cec_0_7 (index: 2)
Match 13: MatchType.NO_MATCH
├── Input
│ └── 歩み (アユミ)
└── Matched Lyrics Tokens
Match 14: MatchType.EXACT_SURFACE
├── Input
│ └── の (ノ)
└── Matched Lyrics Tokens
└── の (ノ) │ lemma: の │ pos: ADP
Match 15: MatchType.MORA_COMBINATION
├── Input
│ └── のろい (ノロイ)
├── Matched Lyrics Tokens
│ ├── あの (アノ) │ lemma: あの │ pos: DET
│ ├── だろう (ダロウ) │ lemma: だ │ pos: AUX
│ └── 改札 (カイサツ) │ lemma: 改札 │ pos: NOUN
└── Mora Trace
├── Mora: ノ → Token: corpus_5d49c4d62cec_0_2 (index: 1)
├── Mora: ロ → Token: corpus_5d49c4d62cec_0_27 (index: 1)
└── Mora: イ → Token: corpus_5d49c4d62cec_0_3 (index: 1)
Match 16: MatchType.MORA_COMBINATION
├── Input
│ └── もの (モノ)
├── Matched Lyrics Tokens
│ ├── もし (モシ) │ lemma: もし │ pos: ADV
│ └── あの (アノ) │ lemma: あの │ pos: DET
└── Mora Trace
├── Mora: モ → Token: corpus_5d49c4d62cec_0_0 (index: 0)
└── Mora: ノ → Token: corpus_5d49c4d62cec_0_2 (index: 1)
Match 17: MatchType.EXACT_SURFACE
├── Input
│ └── は (ハ)
└── Matched Lyrics Tokens
└── は (ハ) │ lemma: は │ pos: ADP
Match 18: MatchType.EXACT_SURFACE
├── Input
│ └── ない (ナイ)
└── Matched Lyrics Tokens
└── ない (ナイ) │ lemma: ない │ pos: AUX
やりたかったけど間に合わなかったこと
クソアプリ Advent Calendar 2025に間に合わなかった検討事項が複数・・・
近日中に追記できそうなら載せます
ヒット曲の「曲が会話するシリーズ」への適性ランキング
日本語コーパス内の文章を歌詞の切り貼りでどれくらい再現できるかで、「曲が会話するシリーズ」に使いやすい曲の傾向やそのような曲を書くアーティストを洗い出す
これについては現在コードを書いているので、でき次第追記するかも。
1. 適当な日本語の小規模・短文コーパスをネットからダウンロード。現状見た限り、ITAコーパスが文章量的にちょうどよさそう?
2. spotifyAPIを使ってここ日本語ヒット曲を検索、artist名・曲名をリストアップ
3. lyric-geniusを用いて、2で取得した曲の歌詞を抽出
4. 1のコーパス内の各文章を今回のプログラムに入力、歌詞を切り貼りして再現する
5. 編集距離を用いて、コーパスの元文章と、歌詞を切り貼りして再現した結果の距離を評価
作業に取り掛かり始めて気づきましたが、今回のアプリだと曲がどの程度モーラを持ってるかで決まるので分析してもあんまり意味ない… 単純に曲のモーラの網羅率で決まりそう。
一応やるだけやってみた感じ、以下のような結果になった。
spotify APIはChartをとるのが不便そう (API経由ではChartにアクセスできずcsvダウンロードのみ、週次・日次ランキングのみで年次が見当たらない)なので、代わりに Billboardをスクレイピングしてます。
タイムスタンプ付き歌詞の抽出
今回のプログラムでは文字列の歌詞を切り貼りしてつなげるため「実際の曲の何分何秒に出てくる歌詞が切り取られてきた」という情報まではわからない。
これを解決するには、WhipserなどのSTTモデルを使って、タイムスタンプ付きの歌詞データを作る方法などが考えられる。

