koudenpaのブログ

趣味のブログです。株式会社はてなでWebアプリケーションエンジニアをやっています。職業柄IT関連の記事が多いと思います。

定例会議でジブリ制作日誌眺めの薦め

僕が所属しているチームでは毎日メンバー全員で集って昼会を行ってる。その重要なアジェンダの一つに『ジブリ製作日誌コーナー』がある。

二十数年前の同日のスタジオジブリ日誌を眺めて雑談し懇親を深めるコーナーだ。

ジブリ日誌は以下のように雑談のネタとしてとても優れている。

  • みんなジブリの名前くらいは知っている
  • 当時の世相を窺い知れる
  • 程よくジェネレーションギャップがある
  • 単純にコンテンツとして面白い
  • 近所に住んでいるので個人的に親近感がある

f:id:koudenpa:20210312123839p:plain
ある日のコーナーの様子

いいコーナーなので機会があれば宣伝している(先日エンジニアの集まる勉強会でチーム紹介した際にも厚く取り上げた)のだけれど、流行の兆しがない。

何故だ?

.NETでMIDIファイルの再生にほぼ失敗した

ので出来事として記録しておく。

https://github.com/7474/SRC を弄るうえで避けて通れないのがMIDIファイルの再生なので少し手を付けてみた。できれば移植性のあるコードで再生したいので依存関係に.NET Standard 2しか入っていないっぽいNAudioを使って試してみた。バーンってMIDI対応してるっぽいこと書いてあるしな!!

github.com

結果、とりあえず再生はできたけれど聴けたものではなかった。

MIDIも音楽もほぼ何も知らない状態なので何も分からん状態。いい感じの再生のためには何か色々な手続きがあるのだろう。

試していた状態のコード。

var midiOut = new MidiOut(0);
var midiFile = new MidiFile(path, false);
var task = Task.Run(async () =>
{
    var sw = new Stopwatch();
    sw.Start();
    var startMillis = midiFile.Events.StartAbsoluteTime;
    foreach (var melist in midiFile.Events)
    {
        foreach (var me in melist)
        {
            var eventTime = me.AbsoluteTime - startMillis;
            if (eventTime - sw.ElapsedMilliseconds > 0)
            {
                await Task.Delay((int)Math.Max(0, eventTime - sw.ElapsedMilliseconds));
            }
            midiOut.Send(me.GetAsShortMessage());
        }
    }
});

NumberOfDevices1 だったので使うデバイスを変えれば音源が変わる環境ではない模様。

> MidiOut.NumberOfDevices
1

おとなしくWindows向けのええ感じのライブラリを使うのがいいのだろう。

NAudioのMIDIもどう見てもWindows専用だしな!!!

NAudio/MidiInterop.cs at master · naudio/NAudio · GitHub

いい感じのソフトウェア音源を.NETマネージドな世界で使えれば、環境非依存でMIDI再生はできそうなものだけれど、需要はなさそう。

Blazor WebAssemblyのログ

がどうなってんのか気になったので少し見ていたメモ。

Blazor WebAssemblyのプロジェクトで依存しているDLLで無造作に.AddDebug()してデバッグログを見ていたのだけれど、Blazor WebAssembly上では特にコンソール上に何か出るわけでも、エラーするわけでもなく動作していたので気になった次第。

とりあえずドキュメントを見てもイマイチ分からなかったので試したりソースを眺めたりした。

バージョンは 5.0.3 で、ここでいうログはサードパーティのものではなく Microsoft.Extensions.Logging です。

基本的にはログレベルだけ設定( builder.Logging.SetMinimumLevel(LogLevel.XXX) )すればいいようだった。それだけでWebAssemblyConsoleLoggerというブラウザのコンソールにログ出力するロガーが構成されるようになっている。

Debugレベル以下はブラウザ*1の側でフィルタされているので、それらを見たい場合は『すべてのレベル』を表示するように設定する必要はあった。

f:id:koudenpa:20210228233354g:plain
Debugレベル以下はブラウザの既定ではフィルタされている

未対応のログプロバイダを追加すると動作しないので、変な設定をしてしまったらその設定を消せばよい。スクリーンショット.AddConsole() してみた場合のもの。

f:id:koudenpa:20210228233317p:plain
未対応のProviderを追加すると例外する

この記事の段階のお試し結果を転記しておく。

public class Program
{
  public static async Task Main(string[] args)
  {
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("#app");
    // Document: https://docs.microsoft.com/ja-jp/aspnet/core/blazor/fundamentals/logging?view=aspnetcore-5.0&pivots=webassembly
    // ドキュメントからだと分かりづらいが、
    // 既定で WebAssemblyConsoleLoggerProvider が構成されており ILogger を注入するだけでコンソールにログ出力を行えるようになっている。
    // https://github.com/dotnet/aspnetcore/blob/d827c653b787c07de908240b7746ce34d3e6271e/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs#L227-L230
    builder.Logging
      // Chromeの開発者ツールの既定では Info レベル以上を表示するようになっている。
      // Debug 以下のレベルを表示するにはブラウザ側の設定も変更が必要。
      .SetMinimumLevel(LogLevel.Trace)
      // 事実上他のプロバイダーは設定しても動かなかったり、設定した瞬間に(ちゃんと)未対応である旨の例外が発生する。
      // 例えばローカルストレージにログを出力する CustomLoggingProvider を実装する、のようなことはできなくもないようだ。
      .AddDebug()
      //.AddConsole()
      //.AddSimpleConsole()
      ;
    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    await builder.Build().RunAsync();
  }
}
@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [Inject]
    private ILogger<Counter> logger { get; set; }

    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
        logger.LogTrace($"LogTrace Count: {currentCount}");
        logger.LogDebug($"LogDebug Count: {currentCount}");
        logger.LogInformation($"LogInformation Count: {currentCount}");
        logger.LogWarning($"LogWarning Count: {currentCount}");
        logger.LogError($"LogError Count: {currentCount}");
    }
}

例えばローカルストレージにログを出力する CustomLoggingProvider を実装する、のようなことはできなくもないようだ。

これはクラッシュログとか貯めておけて使える場面はあるかもしれない。

後は、Application Insights プロバイダの対応状況は普通に気になるな。これは別途試してみるかもしれないけれど、ログというよりは統合的なApplication Insights対応のほうが気になる。

そんな感じだった。

*1:ChromeやEdgeはそうだったが他のブラウザでどうかは定かではない

C#でバイト単位でファイルを読んでみる

社内の勉強会でGoのメモリアロケーションの話があって、最後に『チャレンジ問題』があった。

こんなの。

チャレンジ問題 ファイルの内容すべてをバイト列に読み込む関数 ReadFile(*File).Read などを用いて実装してください ヒント: リアロケーションが起きないようにするにはどうすればいいだろうか

そういえばC#書いててもファイル読み込みとかStreamReaderとか使ってて低レベルな読み込みしないなぁ、どんな感じになのかなぁ、と思ったのでちょっと見てみた。

結果、.NETでIOに使う Stream には Length っていう身もふたもないプロパティがあったのでその分だけメモリを確保すればよかった。

面白みはなかった。

気が向いたらソースコード眺めてみようかな、とは思った*1

Length プロパティはSeekできないStreamだと例外を吐く模様。ローカルファイルの読み込みならSeekできるだけで、ネットワーク越しのアクセスだと多分使えない。IOは奥が深い。

using System;
using System.IO;
using System.Text;

namespace dotnet_sandbox
{
    // ファイルの内容すべてをバイト列に読み込む関数 `ReadFile` を `(*File).Read` などを用いて実装してください
    // ヒント: リアロケーションが起きないようにするにはどうすればいいだろうか
    // 答え合わせは `os.ReadFile` を確認してください
    // Go 1.15 (ioutil) と1.16の実装の違いを確認し、ベンチマークしてみましょう。
    // PrometheusはGo 1.16でテストが落ちるようになりました ([https://github.com/prometheus/prometheus/issues/8403 #8403])。理由を調べてみましょう。
    class Program
    {
        static byte[] ReadToEnd(Stream stream)
        {
            var buf = new Byte[stream.Length];
            var i = 0;
            int b;
            // ReadByte はStreamが終端だと -1 を返すという現代とは思えないレガシーな仕様
            while ((b = stream.ReadByte()) >= 0)
            {
                // アンセーフにしてポインタを進めていったほうが早いのか? 分からん
                // そもそも長さが分かっているなら Read メソッドに buf  を渡せば一気に読めそう
                buf[i++] = (byte)b;
            }
            return buf;
        }

        static void Main(string[] args)
        {
            using (var stream = new FileStream(args[0], FileMode.Open))
            {
                var bytes = ReadToEnd(stream);
                Console.WriteLine($"{args[0]} length = {bytes.Length}");
                Console.WriteLine(Encoding.UTF8.GetString(bytes));
            }
        }
    }
}

*1:こういう時は大体眺めない

MySql.EntityFrameworkCore で ASP.NET Identity

.NET(Entity Framework Core)を使うならSQL Serverを使っておけ、というのが正直なところなのだけれど、MySQLを使ってみたいと思うこともあるだろう。

ので試した。ASP.NETにはASP.NET Identityという認証ライブラリがあって、Webアプリケーション作成時にチェックボックスを入れるだけでその基本的な構成が行われる。僕が一番好きなIdentiyです。

が、そうすると当然ながらSQL Server向けになっており、MySQLで使うにはちょっと手を加えなくてはならない。

とりあえずユーザー登録とログインができるようになるためにやったことをメモしておく。

Entity Framework Coreのコードファーストマイグレーションを使うのが前提です。

続きを読む