【C# visual studio 2022】MCIで音量を変える、音楽のループ

タグの編集
投稿者 祈り星  (社会人) 投稿日時 2023/9/20 19:13:04
まだC#初心者の者です。
ググっても解決できず行き詰ったので質問させてください。
OSはWindows10でvisual studio 2022を使っています。

MCI (Media Control Interface)を使ってBGMとして再生する音楽ファイルの音量の調整と音楽のループ方法について教えてください。
以下のコードが音量調整で重要みたいだということは分かりましたが使い方が分からないというか、自分のコードにどのように落とし込むのかわかりませんでした。
ループに関しては関連するコードすら見つけられませんでした。

mciSendString("setaudio my_sound volume to 100", null, 0, 0);


あと、可能でしたらMCIを使うことに対して意見をください。
一般的ではないとか、古いとか、PCに負荷が掛かるので他がいいだとか、C#においての音楽の知識がなく分からないので何かあればアドバイスをお願いします。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/9/20 21:07:10
> まだC#初心者の者です。
VB でも構わなければ、ここのサイトのサンプル集にも参考になるものがあるかも。
https://www.umayadia.com/vbsample/home/


> 音楽のループ方法について教えてください。
play コマンドの repeat パラメーターです。
https://learn.microsoft.com/en-us/windows/win32/multimedia/play
https://learn.microsoft.com/ja-jp/windows/win32/multimedia/play
https://www.umayadia.com/vbsample/dotnet-Samples151/Sample181RepeatSound.htm


> BGMとして再生する音楽ファイルの音量の調整と
取得は status コマンドの value / left volume / right volume パラメーター
https://learn.microsoft.com/en-us/windows/win32/multimedia/status
https://www.umayadia.com/vbsample/dotnet-Samples151/Sample189GetSoundVolume.htm

設定は setaudio コマンドの volume to~ / left volume to ~ / right volume to ~パラメーター
https://learn.microsoft.com/en-us/windows/win32/multimedia/setaudio
https://www.umayadia.com/vbsample/dotnet-Samples151/Sample190SetSoundVolume.htm

※ モノラルチャネルの場合、right volume は指定しても無視されます。

また setaudio コマンドでは、 off / left off / right off および on / left on / right on で
オーディオ出力のオン・オフができます。
投稿者 祈り星  (社会人) 投稿日時 2023/9/20 23:23:21
書き込みありがとうございます。
VBのサンプルを参考にして色々組み合わせて試したら偶然ですが音楽のループ、音量調整が出来ました。
しかし、もう一つ教えてほしいです。

このサイトにある C# 初級講座 の 第15回 反射するボール2
https://www.umayadia.com/CSStandard/s015BoundBall2.htm

ここの 3-3.サウンド再生のプログラム にある以下のコードではどのように音量調整が出来るようになるのでしょうか?
これだけは成功しませんでした。


//サウンドファイル名をフルパスにします。アプリ本体のある場所のSoundフォルダー以下です。
    fileName = Application.StartupPath + @"Sound\" + fileName;

    //このサウンド再生に一意な名前を付けます。ここではシステム時刻とランダムを利用します。
    string aliasName = $"Alias{DateTime.Now.Ticks}{Random.Shared.Next(1, 100000)}";

    //サウンドを再生します。
    mciSendString($"open \"{fileName}\" alias {aliasName}", "", 0, IntPtr.Zero);
    mciSendString($"play {aliasName}", "", 0, IntPtr.Zero);
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/9/21 10:42:56
現在のターゲット フレームワークは .NET 6 / .NET 7 / .NET 8(RC1) ですか?
それとも .NET Framework 4.7.2~4.8.1 ですか?

 ターゲットフレームワークが異なると、C# で使える文法が幾許か変わってきますので、
出来れば明示していただけると答えやすいです。


> 偶然ですが音楽のループ、音量調整が出来ました。
漠然と『偶然』という言葉で片付けられても判断しにくいです。
「プログラムは思った通りには動かない。書いた通りに動くのだ。」という格言があります。
1 文字違っただけで動作が変わることもありますからね。

どのように書いたのかを示して頂かないことには、
それが正しいコードなのか間違っているのかの添削もできません。
正しいコードになっているかどうかを自己判断できないのであれば、
『現象を再現可能な最低限のコード』を提示したうえで、
そのコードのどの部分について知りたいのかを具体的に示しましょう。


> mciSendString("setaudio my_sound volume to 100", null, 0, 0);
> mciSendString($"open \"{fileName}\" alias {aliasName}", "", 0, IntPtr.Zero);
> mciSendString($"play {aliasName}", "", 0, IntPtr.Zero);

これについても、末尾の引数(第4引数)が、最初の質問では int で、その後の投稿では IntPtr になっていますね。

引数定義は DllImport 宣言時に決定されるものですが、API の 本来の型(C++向け)は
HANDLE 型ですので、int ではなく IntPtr (あるいは nint)で宣言すべきです。
https://learn.microsoft.com/ja-jp/previous-versions//dd757161%28v=vs.85%29
https://pinvoke.net/default.aspx/winmm/mciSendString.html


> ここの 3-3.サウンド再生のプログラム にある以下のコードでは
偶然であれ何であれ、既に調整できたのですよね。
その時のコードと現状のコードで何が違っているのか、もう一度比較してみてください。

また、mciSendString の戻り値の値が何になっているかも確認しましょう。
この戻り値の値はエラーコードであり、0 であれば成功、失敗時は 0 以外になりますので、
0 以外が返されるようであれば、API の呼び方が間違っていることになります。

たとえば 263(=MCIERR_INVALID_DEVICE_NAME) なら、エイリアス名の指定ミス。
https://learn.microsoft.com/ja-jp/windows/win32/multimedia/mcierr-return-values
https://gist.github.com/KirillOsenkov/e9313c2044da26a756c4b56a7f31c428#file-win32constants-cs-L6583

なお、エラーコードを説明文字列に変換するために、 mciGetErrorString API を併用することもできます。


> どのように音量調整が出来るようになるのでしょうか?
問題になっているのは設定と取得のいずれですか?

$"setaudio {aliasName} volume to {factor}" //設定
$"status {aliasName} volume" //取得



取得系のコマンドの場合は、バッファの指定にも気を配りましょう。

(1) 第2引数(バッファ変数)に、十分なメモリを確保した変数を渡します。
 →先に紹介した VB のサンプルでは、最大 253 文字まで取得可能なバッファを String 型で
   用意していましたが、C# では string ではなく StringBuilder を使うべきです。

(2) 第3引数(バッファの長さ)には、第2引数で確保したメモリのサイズを渡します。
 → StringBuilder なら Capacity プロパティを渡すと良いでしょう。


> これだけは成功しませんでした。
少なくも一度は、ループも音量調整もできたのですよね?
それが、別プログラムに組み込んでみたときに成功しなかった、と。

期待動作しないのであれば、どこまでが動作していて、どこで失敗しているのかを追跡しましょう。
エラーになるのであれば、どの行が何というエラーになるのかを示しましょう。
文法エラーでコンパイルすらできないのか、それとも実行時に例外になってしまうのか。
API なら、戻り値の値をチェックしたり、呼び出し直後の Marshal.GetLastWin32Error() を確認するのも有用です。
投稿者 祈り星  (社会人) 投稿日時 2023/9/21 18:11:40
お答えありがとうございます。
現在のターゲットフレームは.NET6です。PCにダウンロードしたのは.NET7になります。

> 漠然と『偶然』という言葉で片付けられても判断しにくいです。
書き込み内容の情報が不足していて申し訳ないです、以下のコードが私が弄ってループと音量調整に成功したコードです。


// 音楽再生〇〇〇
[System.Runtime.InteropServices.DllImport("winmm.dll")]
private static extern int mciSendString(String command,
StringBuilder buffer, int bufferSize, IntPtr hwndCallback);

private string aliasName = "BGM";


private void button1_Click(object sender, EventArgs e)
{
    ongaku();
}


private void ongaku()
{
    string fileName = "C:\\Users\\音楽.mp3";
    string cmd;

    cmd = "open \"" + fileName + "\" type mpegvideo alias " + aliasName;
    if (mciSendString(cmd, null, 0, IntPtr.Zero) != 0)
        return;

    cmd = "play " + aliasName + " repeat";
    mciSendString(cmd, null, 0, IntPtr.Zero);

    cmd = "setaudio " + aliasName + " volume to 300";
    mciSendString(cmd, null, 0, IntPtr.Zero);
}




>問題になっているのは設定と取得のいずれですか?
設定になります。

すみません、これも今更で情けない話ですが英語が読めず、C#以外の言語は分からずなので解説サイト等も正確ではない和訳と知識不足が合わさって理解が出来ませんでした。
今まで習ってきたC#とは書き方?表記?も一部、違っていてそういうのもあります。
魔界の仮面弁士さんが言ってくれたおかげで分かったのは mciSendString の第4引数は IntPtr になること。
mciSendString の戻り値が0であれば成功、それ以外は失敗、失敗時の数字によってどんなエラーなのか、ということです。

目的である音量の設定をするにはmciSendStringの第1引数に"setaudio my_sound volume to 100"を入れて my_sound を aliasName に変えて文字列連結でつなげる、くらいの理解はしました。
しかし、先ほど私が書いたここのサイトのサンプルに置き換えようとすると色々試しましたが、やはりうまくいきません。
すみませんがどこをどうすればいいのか、解説をどうかよろしくお願いします。
以下が今、うまくいっていないコードです。

private void Play()
{
    string cmd;

    //再生するファイル名
    string fileName = Random.Shared.Next(1, 9) switch
    {
        1 => "C2.wav",
        2 => "F2.wav",
        3 => "G2.wav",
        4 => "C3.wav",
        5 => "EF2.wav",
        6 => "AF2.wav",
        7 => "BF2.wav",
        _ => "EF3.wav"
    };
    fileName = Application.StartupPath + @"Sound\" + fileName;


    string aliasName = $"Alias{DateTime.Now.Ticks}{Random.Shared.Next(1, 90000000)}";
    mciSendString($"open \"{fileName}\" alias {aliasName}"null, 0, IntPtr.Zero);
    mciSendString($"play {aliasName}"null, 0, IntPtr.Zero);



    cmd = "setaudio " + aliasName + " volume to 200";
    mciSendString(cmd, null, 0, IntPtr.Zero);

    label4.Text = mciSendString(cmd, null, 0, IntPtr.Zero).ToString();
}

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/9/21 22:41:15
下記はすべて同じ結果を表します。
今回の場合、+ 演算子で繋ぐ方法よりも、$ 付き文字列で埋め込んだ方が対応が分かりやすいかも。

cmd = "open \"" + fileName + "\" type mpegvideo alias " + aliasName;
cmd = @"open """ + fileName + @""" type mpegvideo alias " + aliasName;
cmd = $"open \"{fileName}\" type mpegvideo alias {aliasName}";
cmd = $@"open ""{fileName}"" type mpegvideo alias {aliasName}";
cmd = @$"open ""{fileName}"" type mpegvideo alias {aliasName}";
cmd = $"""open "{fileName}" type mpegvideo alias {aliasName}""";
cmd = $"""
    open "{fileName}" type mpegvideo alias {aliasName}
    """;

ちなみに 「3 つ以上の"」で囲む記法は、C# 11 で導入された『生文字列リテラル』という構文です。
文字列中に " や改行を、エスケープ処理無しで直接埋め込めるというメリットがあります。

※ .NET 7 のプロジェクトは、既定で C# 11 となります。
※ それ以前のバージョンで C# 11 の構文を使う場合は、プロジェクトの LangVersion 設定を変更する必要があります。


> 以下が今、うまくいっていないコードです。
> fileName = Application.StartupPath + @"Sound\" + fileName;
fileName 変数の使いまわしは良くないですね。
フォルダー名を含むかどうかという点が変化しているので、変数名と合致しなくなってしまう…。
また、パスをつなぐ場合は System.IO.Path.Combine を使った方が望ましいです。
var fullPath = Path.Combine(Application.StartupPath, "Sound", fileName);


> やはりうまくいきません。
これは対象が .wav ファイルだからです。手元では試していませんが、失敗した方では、
API の戻り値が 261 (MCIERR_UNRECOGNIZED_COMMAND) になっていませんでしたか?

setaudio コマンドが使えるのは、デジタル ビデオおよび VCR デバイスに限られます。
https://learn.microsoft.com/ja-jp/windows/win32/multimedia/setaudio
>>> setaudio コマンドは、オーディオの再生とキャプチャに関連付けられている値を設定します。
>>> デジタル ビデオおよび VCR デバイスはこのコマンドを認識します。

これはつまり、.mp3 ファイル(MPEGViedeo) などには使えますが、
.wav ファイル(WaveAudio) などには使えないコマンドであることを意味します。
(うまくいった方のコードは「type mpegvideo」でしたよね)

拡張子ごとのデバイスタイプは、レジストリの
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\MCI Extensions
に記載されています。

ということで、MCI では waveaudio デバイスの音量制御ができません。
.wav ファイルを .mp3 などに変換して利用することを検討してみてください。

もしも .wav ファイルをそのまま扱いたいのであれば、
MCI ではなく、WaveOut API を利用するという手法があります。
(波形データを直接扱う低レベル命令なので、それなりに面倒です)
この場合、ボリューム設定は waveOutSetVolume API です。
もしくはミキサー API を使って、大元のボリュームを変更するかですね。

その他、DirectSound で再生する手法もあります。
この場合は IDirectSoundBuffer の SetVolume メソッドですね。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/9/22 18:10:34
> これはつまり、.mp3 ファイル(MPEGViedeo) などには使えますが、
> .wav ファイル(WaveAudio) などには使えないコマンドであることを意味します。

下記の投稿(C++)を見る限りでは、ファイルを open する際に
明示的に type mpegvideo パラメーターを指定することで、
$"setaudio {aliasName} volume to {factor}" が使えるかもしれません。
https://okwave.jp/qa/q7789110.html

当方未検証のため、確証はまだないですけれどね。
投稿者 祈り星  (社会人) 投稿日時 2023/9/22 20:19:38
お答えありがとうございます。

> fileName 変数の使いまわしは良くないですね。
確かに、以後はちゃんと変えて使っていきます。
教えてくれた Path.Combine も簡単で便利ですね。

> これは対象が .wav ファイルだからです。
確かに・・・mp3で試したらちゃんと鳴って音量も変わりました。
分かりやすく説明もありがとうございます。

> 当方未検証のため、確証はまだないですけれどね。
試したらwavでも音量が変わりました、これはありがたいです。
拡張子にあまり振り回されずに音楽ファイルを使っていけそうです。

目的の音量調整、ループが出来て知識も増えました。
これで質問は終わろうと思います。
何度もお付き合い本当にありがとうございました。