Raspberry Pi PicoでAT-Xを作る⑭ ランダム再生を実装する

raspberry pi pico

AT-Xは一挙放送じゃなかったらランダム再生みたいなもんだよな。

運が悪かったら最終回から再生される可能性もあるのウケる。

テスト用のフォルダ構成

アニメのタイトルがついたフォルダを作った

それぞれのフォルダにはOPとEDを入れた

乱数発生器でランダムにファイルを選ぶ

実装

main()の記述(一部抜粋)

string root_path = "0:/anime/";
mt19937_64 mt64(0); // 乱数生成器
vector<string> title_list; // ファイルパスリスト
vector<string> played_list; // 再生済みのファイルパスリスト

    // microSD内のファイルリストを作成
    create_title_list();

    while (true) {
        // ランダムにファイルパスを取得
        string filepath = get_random_file();
        
        // ファイルを再生
        play_video(filepath);
    }

全ファイルのリストを作る関数

// フォルダ名を取得してソートする
void create_title_list(){
    DIR dir, title_dir;
    FILINFO fno;
    // 動画データを格納してるディレクトリを開く
    fr = f_opendir(&dir, "/anime");
    if (fr != FR_OK) {
        printf("ディレクトリのオープンに失敗しました。エラーコード: %d\r\n", fr);
        return;
    }

    while(true){
        fr = f_readdir(&dir, &fno);
        if(fr != FR_OK) {
            printf("ディレクトリの読み取りに失敗しました。エラーコード: %d\r\n", fr);
            break;
        } else if (fno.fname[0] == 0) {
            // ファイル名が空なら終了
            printf("最後のフォルダに到達しました。\r\n");
            break;
        }

        if (fno.fattrib & AM_DIR) { // ディレクトリかどうかをチェック
            //隠しフォルダとかは無視
            if (fno.fname[0] != '.' && fno.fname[0] != '..') {
                string title = fno.fname;

                // タイトルのディレクトリを開く
                fr = f_opendir(&title_dir, (root_path + title).c_str());
                if (fr != FR_OK) {
                    printf("タイトルディレクトリのオープンに失敗しました。エラーコード: %d\r\n", fr);
                    continue; // 次のディレクトリへ
                }

                while (true) {
                    fr = f_readdir(&title_dir, &fno);
                    if(fr != FR_OK) {
                        printf("タイトルディレクトリの読み取りに失敗しました。エラーコード: %d\r\n", fr);
                        break;
                    } else if (fno.fname[0] == 0) {
                        // ファイル名が空なら終了
                        printf("タイトルディレクトリの最後に到達しました。\r\n");
                        break;
                    }

                    if(!(fno.fattrib & AM_DIR)){
                        string filename = fno.fname;
                        // タイトルリストに追加
                        title_list.push_back(title + "/" + filename);
                        printf("タイトル: %s, ファイル名: %s を追加しました。\r\n", title.c_str(), filename.c_str());
                        files_count++;
                    }
                }
                f_closedir(&title_dir);
            }
        }
    }
}

ランダムにファイルパスを取得する関数

// ランダムにファイルを取得する
string get_random_file() {
    if (files_count == 0) {
        printf("タイトルリストが空です。\r\n");
        return "";
    }
    while (true) {
        uniform_int_distribution<int> dist(0, files_count - 1);
        int random_index = dist(mt64);
        string selected_title = title_list[random_index];

        // ランダムに選ばれた内容がplayed_listに含まれてた場合
        if(find(played_list.begin(), played_list.end(), selected_title) != played_list.end()) {
            // 全ファイル再生済みの場合
            if (played_list.size() == files_count) {
                printf("全てのファイルを再生済みです。\r\n");
                played_list.clear(); // 再生済みリストをクリア
            }
            continue;
        }
        // ランダムに選ばれた内容がplayed_listに含まれていなかった場合
        played_list.push_back(selected_title);
        printf("選択されたタイトル: %s\r\n", selected_title.c_str());
        return selected_title;
    }
}

結果

何回か電源をONにして最初に再生されるファイルを確認しよう!

1回目

「藍より青し」EDが再生された。

2回目

「藍より青し」EDが再生された。

3回目

「藍より青し」EDが再生された。

絶対に「藍より青し」EDが再生されてしまう・・・
これじゃダメだ~



ラズパイピコには現在時刻を取得する機構がないから、
毎回同じシード値で乱数を発生させているみたい

別の方法を調べた。

何も接続していないピンのノイズをシート値にする

実装

自然に発生するノイズの一部をピックアップしてその値をシード値として使う方法があるらしい。

main()の記述(一部抜粋)

    // microSD内のファイルリストを作成
    create_title_list();

    // 乱数生成のシード値を取得
    uint32_t seed = get_seed_from_noise();
    // 乱数生成器を作成
    mt19937_64 mt64(seed);

    while (true) {
        // ランダムにファイルパスを取得
        string filepath = get_random_file(mt64);
        
        // ファイルを再生
        play_video(filepath);
    }

ノイズからシード値を取得する関数

// ノイズからシードを取得する関数
uint32_t get_seed_from_noise() {
    adc_init();
    adc_select_input(0); // ADC0(26pin)

    uint32_t seed = 0;
    for (int i = 0; i < 32; ++i) {
        seed = (seed << 1) | (adc_read() & 0x01); // ADCから1ビット読み取り
        sleep_us(1); // 少し待機してノイズを取得
    }
    printf("取得したシード: %u\r\n", seed);
    return seed;
}

結果

1回目

「君と僕。」OPが再生された。

2回目

「先輩はおとこのこ」OPが再生された。

3回目

「藍より青し」EDが再生された。

これはうまくいったタイミングの3回を切り取っただけで、
もちろん乱数らしく3連続で同じファイルが選択されることとかもあった。

2つ目以降に選択されるファイル

ここまでは
「どうやって起動するたびにシード値を変えるか」の話。

ここからは
「次に再生されるファイルも本当にランダムになってるか」の話。

観たほうが早いから動画撮った。
↓こんな感じ

再生が済んだものは別のリストに突っ込んでいって、
選択肢から排除してるから重複もなくランダムに選択されていってる

いい感じ

おわりに

そろそろ音声出力を考えないといけないな・・・

タイトルとURLをコピーしました