PCとTVをオーディオケーブルで繋ぎ,アニメの主題歌や挿入歌をキャプチャし,歌詞を手打ちし,アーカイブするのが趣味?のはず?です.始めたのは恐らく23年前,高専に入学し,初めてのPCを買い,周りの連中の影響でアニヲタイズした頃です.現時点では約7600曲が溜まっています.
一覧をExcelファイルで管理しています.このExcelのマクロで,以下3種類のデータを更新・生成しています.
- 歌詞HTML(ヘッダの曲名等を更新)やMP3へリンクするHTML(番組名等含む表形式)
- コレクションしているリストのHTML(ホームページ掲載用)
- PC操作中にBGMとしてランダム再生するプレイリスト
■プレイリスト生成ロジック改良
今回,プレイリスト生成のロジックを改良しようと思い立ちました.現在は,プレイリストに追加するかどうかを示すフラグの列を設け,そこが「O」になっているMP3ファイルのパスのみをプレイリストへ出力するようにしています.このフラグは,ホームページへ掲載するリストHTMLを更新するタイミングで,1年以上前の曲を「X」に変える運用にしています.これにより,直近1年の曲のみが再生されることになります.

しかし,たまにはもっと昔の曲と再会し,懐かしくなりたいなと.そこで,以下の要領でピックアップした曲をプレイリストへ出力したいと思います.
- 直近6箇月は全て登録する.
- それ以前は古いほど少なく,新しいほど多くなるような割合でランダムに採用する.
- 全部で1000曲をプレイリストへ出力する.
■収録日の一覧化
そのためにはまず,収録日を一覧化する必要があります.大量なので,いちいち調べて手入力するのは非現実です.それだけで心折れちゃう.いろいろ思案した結果,MP3ファイルの最終更新日をピックアップすれば良いのではと.こんな単純なことに,なかなか思い至れませんでした.灯台下暗し.
マクロでの実装は簡単.最終更新日というか,ファイルの新しさが知りたいので,最古のデータを0とした月数を算出するようにしましょう.
Const AGE_ORIGIN As Integer = 2002 * 12 + 3 Dim fso As New FileSystemObject Dim f As File Dim age As Integer Set f = fso.GetFile("xxx.mp3") age = Year(f.DateLastModified) * 12 + Month(f.DateLastModified) - AGE_ORIGIN
VBAがあまりに久しぶりすぎて,参照するライブラリ名「Microsoft Scripting Runtime」を思い出せませんでした.最古のデータは2002年3月で団子状になっていました.MP3なるものが出回り始めたこの頃,Wave(PCM)から一気にまとめて変換したんでした.これだとそれ以前の収録日情報が不正確になりますが,そこまでの精度は不要でしょう.
■加重ランダム採用
難しいのは,新しさに応じた重みでランダム抽出する方法です.VBAのRndメソッドが返す0以上1未満の乱数は,一様分布に従うものだと思います.これをどう活用するか.三つの方法を考えました.以下の5曲が候補に残っていて,そこから3曲を選ぶ例で説明したいと思います.
- 曲A:月数1
- 曲B:月数1
- 曲C:月数2
- 曲D:月数2
- 曲E:月数4
■加重ランダム採用方法#1
最初に考えたのは,乱数を一つ算出し,それに対応する積算月数の曲を採用する,というのを繰り返す方法です.
曲 | 月数 | 積算月数 | 採用条件 | 採用率 |
---|---|---|---|---|
A | 1 | 1 | Rnd*10<1 | 10% |
B | 1 | 2 = 1+1 | 1≦Rnd*10<2 | 10% |
C | 2 | 4 = 1+1+2 | 2≦Rnd*10<4 | 20% |
D | 2 | 6 = 1+1+2+2 | 4≦Rnd*10<6 | 20% |
E | 4 | 10 = 1+1+2+2+4 | 6≦Rnd*10<10 | 40% |
Rndメソッドの戻りが0.5だった場合,曲Cが採用されます.そして次の1曲は,以下の方法で採用されます.という風に繰り返す方法.
曲 | 月数 | 積算月数 | 採用条件 | 採用率 |
---|---|---|---|---|
A | 1 | 1 | Rnd*8<1 | 12.5% |
B | 1 | 2 = 1+1 | 1≦Rnd*8<2 | 12.5% |
D | 2 | 4 = 1+1+2 | 2≦Rnd*8<4 | 25% |
E | 4 | 8 = 1+1+2+4 | 4≦Rnd*8<8 | 50% |
この方法の問題は,処理時間がかかることです.1曲選ぶたびに積算月数を再計算する必要があります.計算量は候補数×採用数のオーダーです.実行に1分以上かかりました.これは嫌.
■加重ランダム採用方法#2
次に考えたのは,月数に基づく確率で各曲の採用是非を調べていく方法です.
曲 | 月数 | 採用条件 | 採用率 |
---|---|---|---|
A | 1 | Rnd<0.1 = 1/(1+1+2+2+4) | 10% |
B | 1 | Rnd<0.1 = 1/(1+1+2+2+4) | 10% |
C | 2 | Rnd<0.2 = 2/(1+1+2+2+4) | 20% |
D | 2 | Rnd<0.2 = 2/(1+1+2+2+4) | 20% |
E | 4 | Rnd<0.4 = 4/(1+1+2+2+4) | 40% |
- 乱数を算出し,0.1未満だったら曲Aを採用する.
- (曲Aを採用したかどうかに関わらず)乱数を算出し,0.1未満だったら曲Bを採用する.
- (曲AやBを採用したかどうかに関わらず)乱数を算出し,0.2未満だったら曲Bを採用する.
- 以下同様
この方法だと,一巡では採用数に達しないことが多いです.実際は分母がもっともっと大きいので,なかなかこの採用条件が満たされないのです.そのため,方法1ほどではないにせよ,何度か回す必要があります.例えば,1巡目で曲BとDが採用された場合は,2巡目で下表のチェックをしていくことになります.
曲 | 月数 | 採用条件 | 採用率 |
---|---|---|---|
A | 1 | Rnd<0.1 = 1/(1+2+4) | 14% |
C | 2 | Rnd<0.2 = 2/(1+2+4) | 29% |
E | 4 | Rnd<0.4 = 4/(1+2+4) | 57% |
やっぱり処理時間,かかりました.全部で600巡くらい必要で,十数秒かかりました.これもちょっと嫌.
■加重ランダム採用方法#3
最後に考えたのは,方法2をベースに,残採用数が多いほど採用条件を緩める方法です.
- 曲Aの採用条件を「Rnd<0.3 = 1/(1+1+2+2+4)*残採用数3」とする.
- 乱数が0.3未満だった場合
- 曲Aを採用する.
- 曲Bの採用条件を「Rnd<0.2 = 1/(1+1+2+2+4)*残採用数2」とする.
- 乱数が0.3以上だった場合
- 曲Aを採用しない.
- 曲Bの採用条件を「Rnd<0.3 = 1/(1+1+2+2+4)*残採用数3」とする.
この方法でも,一巡では採用数に達しませんでした.何度か回す必要があります.とはいえ,加速項のお陰で,5巡くらいで済みました.処理時間は数秒程度.いいね.
こうして方法3でプレイリストに採用された曲の月数統計をグラフ化しました.良い感じです.頭痛に見合う価値があります.

みたいなことを眠い中やっつけるのが会社での私の仕事の一つです.上記の数理統計学的な理論立てが合っていたとしても,プログラムへの落とし込みに間違いがあると台無し.眠い中,どっちに誤りがあるのか探したりするの辛い.どっちもが合っていたとしても,目標とする結果に達していなければ,別の案を捻りだす必要があります.フレーフレー私.