ひろばニュース|2026/07/31~2026/08/06号

みかん

お待たせしました!今週のニュースです!新着などのお知らせです!

みかんの新着おんがく紹介①|フリーBGM『みかんのテーマ』『平原』など!
15秒フリーソング:みかんのテーマ
フリーBGM|ループ対応、用途:Youtube動画向け、Shorts・Tiktok向け、ゲーム・アプリ向け、15秒CM向け、時間:0分15秒、BPM:127、キー:C、ジャンル:あかるい、楽器:ボーカル、トイミュージック|15秒BGM第61弾!みかんをテーマにしたボーカルソングです!動画の導入やコミカルなシーン、OP、ED、場面転換でのシュールな歌ものなどにぴったり!
フリーBGM:平原
フリーBGM|ループ対応、用途:Youtube動画向け、ゲーム・アプリ向け、時間:1分04秒、BPM:89、キー:C、ジャンル:あかるい、楽器:ファミコン|レトロで懐かしいファミコン風BGMです!平和な平原のような、待機画面や移動中のシーンなどにぴったり!
みかん

7月11日に、みかんのおんがくひろば初のボーカル曲『みかんのテーマ』を公開しました!何となくほんわかしたシーンなどに流すのにぴったりなのでぜひ聞いてみてください!

みかんの新着おんがく紹介②|今週の15秒&30秒BGMはこちら!
15秒フリーBGM:グランドパレード(リミックス ver.)(15秒 ver.)
15秒フリーBGM|ループ対応、用途:Shorts・Tiktok向け、ゲーム・アプリ向け、15秒CM向け、時間:0分15秒、BPM:125、キー:E、ジャンル:おしゃれ、あかるい、楽器:オーケストラ|15秒BGM第63弾!オーケストラ風に仕上げた、パレード系な一曲『グランドパレード』のリミックスバージョンです!感謝祭やフェスティバル、フィナーレのシーンなどの楽しくて盛り上がる場面にぴったりです!
30秒フリーBGM:雨だれ
30秒フリーBGM|ループ対応、用途:Youtube動画向け、Shorts・Tiktok向け、時間:0分30秒、BPM:88、キー:Bm、ジャンル:ゆったり、おしゃれ、みらい、楽器:シンセサイザー|30秒BGM第43弾!SF系アンビエントで、近未来都市の雨や廃墟のBGMにぴったりです!
みかん

今週も15秒&30秒BGMを追加しました!他にもあるのでぜひ見てみてください!

いろはの新着セットリスト|『チェックメイト』×『降下』
15秒フリーBGM:チェックメイト
フリーBGM|ループ対応、用途:Youtube動画向け、Shorts・Tiktok向け、ゲーム・アプリ向け、15秒CM向け、時間:0分15秒、BPM:126、キー:A#m、ジャンル:ゆったり、楽器:シンセサイザー|15秒BGM第60弾!緊迫シーン、薄ら寒いような焦燥感をイメージした1曲です!解説動画、実況動画でも瀬戸際っぽいような場面にぴったり!
フリー効果音:降下
フリー効果音|ジャンル:かんきょう|飛行機や宇宙船の降下、ランディングをイメージして再現しました!風がぶわってくる感じがポイントです!
いろは

今週のセットリスト案のテーマは『緊迫のワンシーン』です。風のような効果音であるこの『降下』にこの『チェックメイト』の金属音とシンセの組み合わせのBGMを合わせることで、隙間風が吹き抜けていくような緊迫感が演出できますわね。

ことはの新着ソースコード|『みかんのリズムスクエア』をアップデートしました!
/**
 * ====================================================================
 *  【WASM ➔ JS 連携】wasm-adapter.js ロジック一連処理
 * ====================================================================
 */

export class WasmAdapter {
    constructor() {
        this.wasmInstance = null;
        this.wasmMemory = null;
        this.isLoaded = false;

        // C++(WASM)側のメモリの「住所(ポインタ)」を保持する変数
        this.notesTimePtr = 0;
        this.notesLanePtr = 0;
        this.progressPtr = 0;
        this.statusPtr = 0;
        this.notesCount = 0;
    }

    /**
     * STEP 1: WASMモジュールのロードと初期化
     * -------------------------------------------------------------
     * ブラウザにWASMファイルを読み込ませ、メモリのコントロール権を取得
     */
    async init(wasmUrl = 'wasm/notes.wasm') {
        try {
            // WASM側への最小限のインポートオブジェクトを定義
            const importObject = {
                a: { a: (delta) => console.log("resize_heap:", delta) },
                env: {
                    abort: () => console.error("Wasm aborted"),
                    js_log: (val) => console.log("[Wasm Log]:", val)
                }
            };

            const response = await fetch(wasmUrl);
            const bytes = await response.arrayBuffer();
            const results = await WebAssembly.instantiate(bytes, importObject);

            this.wasmInstance = results.instance;
            this.wasmMemory = this.wasmInstance.exports.b; // WASMの生メモリ(ArrayBuffer)を取得!
            this.isLoaded = true;

            console.log("⚡ [WasmAdapter] WASMメモリの取得に成功しました。");
            return true;
        } catch (error) {
            console.error("❌ [WasmAdapter] 初期化失敗:", error);
            this.isLoaded = false;
            return false;
        }
    }

    /**
     * STEP 2: JSからWASMの生メモリへデータを「ゼロコピー」する。
     * -------------------------------------------------------------
     * WASM側に領域を生成し、JSの型付き配列(TypedArray)を被せて書き換え。
     */
    loadNotes(notes, bpm = 120) {
        if (!this.isLoaded) throw new Error("WASMが初期化されていません。");

        this.notesCount = notes.length;
        const exports = this.wasmInstance.exports;

        // 1. 古い譜面データがメモリに残っていたら、C++側でクリーンアップ
        if (exports.g) exports.g(); 

        // 2. C++側のメモリ空間に、データの格納エリアを「ポインタ(住所)」として確保
        this.notesTimePtr = exports.d(this.notesCount);  // Float64Array用のメモリ確保
        this.notesLanePtr = exports.e(this.notesCount);  // Int32Array用のメモリ確保
        this.progressPtr  = exports.f(this.notesCount);  // Float32Array用のメモリ確保(進捗率用)
        this.statusPtr    = exports.e(this.notesCount);  // Int32Array用のメモリ確保(判定フラグ用)

        // 🎯WASMの生のバッファの上に、JSの型付き配列のビューを重ね合わせる(オーバーレイ)
        const timeArray   = new Float64Array(this.wasmMemory.buffer, this.notesTimePtr, this.notesCount);
        const laneArray   = new Int32Array(this.wasmMemory.buffer, this.notesLanePtr, this.notesCount);
        const statusArray = new Int32Array(this.wasmMemory.buffer, this.statusPtr, this.notesCount);

        // 3. 重ね合わせた配列に値を代入し、そのままWASM(C++)側のメモリを書き換え!
        for (let i = 0; i < this.notesCount; i++) {
            timeArray[i] = notes[i].time;
            laneArray[i] = notes[i].lane;
            statusArray[i] = 0; // 0: 未判定
        }

        // 4. データが入ったメモリの「住所」をC++のゲームエンジンに渡して初期化完了
        exports.h(this.notesCount, bpm, this.notesTimePtr, this.notesLanePtr, this.progressPtr, this.statusPtr);
        console.log(`🎵 [WasmAdapter] ${this.notesCount} 個のノーツをWASM直結メモリへ転送しました。`);
    }

    /**
     * STEP 3: 毎フレームの超高速位置計算(ゲームループ内での呼び出し)
     * -------------------------------------------------------------
     * 1秒間に60〜120回走るループ。重い計算はすべてC++側で一瞬で終わらせ、
     * 画面に描画すべきアクティブなノーツの情報だけを厳選してJSに送り返す。
     */
    update(currentTime) {
        if (!this.isLoaded || this.notesCount === 0) return [];

        const exports = this.wasmInstance.exports;

        // 1. C++側で全ノーツの現在の位置(進捗率)を一斉に計算
        exports.i(currentTime);

        // 2. 最新の計算結果が格納されているWASMメモリのビューを作成
        const progressArray = new Float32Array(this.wasmMemory.buffer, this.progressPtr, this.notesCount);
        const statusArray   = new Int32Array(this.wasmMemory.buffer, this.statusPtr, this.notesCount);
        const laneArray     = new Int32Array(this.wasmMemory.buffer, this.notesLanePtr, this.notesCount);

        const activeNotes = [];
        // 3. 画面内に見えている(描画が必要な)ノーツだけをシャッフルして抽出
        for (let i = 0; i < this.notesCount; i++) {
            if (progressArray[i] >= 0.0 && progressArray[i] <= 1.2 && statusArray[i] === 0) {
                activeNotes.push({
                    index: i,
                    progress: progressArray[i], // 0.0(画面上端)〜1.0(判定ライン)
                    lane: laneArray[i],
                    status: statusArray[i]
                });
            }
        }

        return activeNotes; // app.jsのCanvas描画処理へ。
    }

    /**
     * STEP 4: 打鍵判定
     * -------------------------------------------------------------
     * ユーザーがキーを押した瞬間に実行。難読化されたC++関数を綺麗にラッピング。
     */
    checkHit(lane, currentTime) {
        if (!this.isLoaded) return { result: 'NONE', noteIndex: -1, diff: 0 };

        const exports = this.wasmInstance.exports;

        // 1. ユーザーが叩いた「レーン」と「正確な時間」をC++で判定。
        const hitResult = exports.j(lane, currentTime); // 難読化関数「j」を実行
        const hitIndex  = exports.k();                  // 叩いたノーツのインデックス「k」を取得
        const timeDiff  = exports.l();                  // 判定ラインとの正確なタイムラグ「l」を取得

        // 2. C++側の数値(1, 2, 3)を、JS側で扱いやすい文字列に翻訳して返却
        let judgeStr = 'NONE';
        if (hitResult === 1) judgeStr = 'PERFECT';
        else if (hitResult === 2) judgeStr = 'GOOD';
        else if (hitResult === 3) judgeStr = 'MISS';

        return {
            result: judgeStr,
            noteIndex: hitIndex,
            diff: timeDiff
        };
    }
}
ことは

みかんのおんがくひろばのミニゲーム『みかんのリズムスクエア』をアップデートしました。4レーン方式かつサイバーなデザインをイメージしたリズムゲームです。ゲームエンジンとしてWasmを利用しました。以下にその要点をまとめたのでぜひ一読ください。また、上記のソースコードはWasm(C++)とJavascriptの連携処理についてまとめたものです。参考にどうぞ。

1.C++(WebAssembly)がゲームの進行管理や判定処理などの演算処理を担当。JavaScript単体での実行に比べ、処理を高速に。
2.オーディオの再生位置(currentTime)に依存せず、より高精度な時刻同期のできるperformance.now()基準値を採用。音ズレのない、正確なノーツ処理を実現。
3.ノーツの描画やエフェクト処理において、HTMLのDOMによるオーバーヘッドを防止。Canvas上に毎秒60〜120回以上の高フレームレート(FPS)でレンダリングを行い、ラグのない描画を実現。

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