ASP.NET Core Blazor WebAssembly でメトロノームを作る その1(とりあえず動くもの作る編)

  • C#
  • Blazor

ASP.NET Core Blazor WebAssembly、つい最近触り始めましたが面白いです。
JavaScriptはたまにしか書かないのでいざ書く必要に迫られたときにイイ感じのコードが中々書けないですが、
BlazorならC#を使えるのでやりたいと思った処理がすぐ書けて良いですね。

というわけで練習がてら、メトロノームを作ってみることにしました。
メトロノームを選んだ理由は、非同期処理を試せそうなのと、
私が趣味でギターを弾くので音楽に関連するものを作ったみたかったからです。

要件定義

まず、メトロノームを作るためにどんな機能が必要か簡単にまとめます。

  • BPMを指定できるようにする。タップテンポで設定できればなお良し。
  • 拍子(4拍子とか3拍子)を指定できるようにする
  • 何拍目か分かるような表示をする
  • 1拍毎に音が鳴るようにする。拍子の頭は違う音だとなお良し。
  • 1つのボタンで開始/停止を制御

こんな感じでしょうか?
流石に一気に全て実装するのは大変なので今回は、

  • BPMを指定できるようにする
  • 拍子を指定できるようにする
  • 数字で何拍目かを表示
  • 開始のみ(停止機能なし)

くらいにしてみようと思います。

実装

というわけでさっそく実装してみましょう。

プロジェクトテンプレートでプロジェクトを作成すると以下のようなプロジェクトが作成されます。

Pagesディレクトリに以下のコンポーネント(Metronome.razor)を追加して、

@page "/metronome"

<h3>Metronome</h3>

<div>
    <span id="click">@click</span>
</div>
<div>
    <input id="bpm" @bind="bpm" /><label id="bpm">BPM</label>
</div>
<div>
    <input id="beat" @bind="beat" /><label id="beat">拍子</label>
</div>

<div>
    <button class="btn btn-primary" @onclick="StartAsync">Start</button>
</div>

@code
{
    private int bpm = 100;
    private int beat = 4;
    private int click = 0;

    async void StartAsync()
    {
        // BPMから1拍の秒数を求める
        var interval = TimeSpan.FromSeconds(60.0 / this.bpm);

        while (true)
        {
            // 拍子の終わりまでインクリメント
            if (this.click++ >= this.beat)
            {
                // 頭に戻る
                this.click = 1;
            }

            // 1拍分待機
            await Task.Delay(interval);
        }
    }
}

このページをナビゲーションメニューから呼び出せるように、NavMenu.razorを以下のように書き換えます。

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorSample</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="metronome">
                <span class="oi oi-warning" aria-hidden="true"></span> Metronome
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

画面イメージはこんな感じです。

何拍目かを表示する<span>と、BPMと拍子を指定する<input>
メトロノームをスタートさせる<button>があるだけのシンプルなページです。
ボタンを押したらStartメソッドを呼んでメトロノームを開始する、といった感じです。

これで実行してみます。

拍の表示が最初の1回以降変わっていないですね。。。

どうやら、ロジック側で明示的に画面表示を更新したい場合は、
StateHasChangedメソッドを使ってステートが変更されたことを通知する必要があるみたいです。

clickフィールドの値を設定した後にStateHasChangedメソッドを呼ぶようにして再度実行してみると...

// 拍子の終わりまでインクリメント
if (this.click++ >= this.beat)
{
    // 頭に戻る
    this.click = 1;
}
StateHasChanged();

上手くいきました!
画面表示の更新はもっとスマートなやり方があるかもですが、とりあえず動くものが出来たので良しとします。

まとめ

私のようなJavaScript苦手系C#erにとっては、JavaScript無しでこんなWebアプリが作れるのは嬉しい限りですね。
使おうと思えば.NET側からJavaScriptの処理も呼び出せるみたいなので、上手く組み合わせればもっと出来ることが広がると思います。
次回は音を鳴らしたりしてみる予定です、お楽しみに~