るいもの戯れ言

ショパンのノクターンの代名詞とも言える有名な曲。

構成は単純だけど、楽譜上、作曲者の指示が非常に多く、その1つ1つをどう表現するかが腕の見せどころとなる。第1小節目、左手にはスタカート指定。

続く小節にはスタカート無し。Sempreも無しなので、第1小節だけだ。

ただダンパーペダルを踏む指示もあるので、踏み込んでスタカートとなれば聴感上はスタカートあっても無くても変わらない。なので気にしないという考えもあるだろう。実際そう弾いているピアニストも多い。しかしわざわざ、このように楽譜上の表現を変えているのだから、やはりスタカートをきちんと生かすべきだろう。つまりスタカートの部分ではダンパーペダルは、踏まないかハーフペダルとしてスタカートを生かすべきだ。

最後のクライマックスにも同様なスタカーティシモ指定。

ここもペダルを踏み込んでスタカーティシモでは違いが分からないわけで、さりとて、全くペダルを踏まずにスタカーティシモでは興醒めなので、うまい具合にハーフペダリングして軽く残響を残しつつ、スタカーティシモを表現することになる。こんな微妙なダンパーペダルの制御をリアルなピアノでやろうとしたら、普段使っているピアノを持ち歩かないと無理だろう。

ところで、ここにStrettoがあるのは今まで知らなかった、確かにこの箇所の演奏は若干テンポを上げるケースが多いなとは思っていたが、これはちゃんと楽譜に指示があったのか。

楽譜引用はエキエル版

コメントを書き込む

GTK+のGtkScrolledWindowを使っている時に、スクロールバーが表示されなかったり、無用な描画要求が多量に来て困っていたのだが、Gladeの設定にあるOverlay Scrollingというオプションをオフったら、嘘のように直った。なんなんだろう、これ。

コメントを書き込む
#195
2017/01/20 07:10

Cairoを使ってアプリケーションのクライアント領域を描画しているのだが、再描画が必要な領域を知るため、最初は、cairo-clip-extentsを使っていた。ただ、これだとどうも、毎度全域を描画するように指示がくる。

もう1つ、cairo_copy_clip_rectangle_listというAPIもあり、こちらはより細かく、再描画が必要な複数の矩形領域を返してくれる。これら2つを表示してみると、こんな感じ。


cairo-clip-extents: (855, 91, 862, 817)
cairo_copy_clip_rectangle_list[0]: Some(cairo_rectangle_t { x: 855, y: 91, width: 7, height: 726 })

cairo_copy_clip_rectangle_listをそのまま使うか、これで返ってきた領域の和集合を計算して、そこに対して再描画するのが良さそう。

コメントを書き込む
#194
2017/01/20 06:49

ショパン・ノクターンを出したのだけど、相変わらずSoundCloudは何でもかんでも海賊版警告が出て面倒なのでupするのやめた。日本でもSpotifyが始まったので、そちらでどうぞ(無料会員でもCMが入るけど聴けます)。

ただ、Spotifyは、かなりキツ目のダイナミック・レンジ圧縮が勝手に入ってしまって、特にクラシック音楽は音質が落ちてしまうのが難点。Apple Musicとかに入っている方は、そちらの方が良いかも(他のディストリビューションはこちら)。

今回は、作品9から37までの12曲。楽譜はエキエル版を参考にした。全般にテンポ指定がかなり速め。このテンポ通りに弾いているピアニストは、あまりいないんじゃないだろうか。でも本当は、もっとサラっと弾くべきなのかもしれない。

第1曲。全音の楽譜では、ほぼ全域でダンパーペダルを踏む指示なのだけど、エキエル版では、ごく一部にしかない。1つ目は出だし。

第1小節(弱起なので)にダンパーベダルがあるが、それっきり(Sempre指定も無し)。そして中間部も出だしにあるのみ。

特に、この中間部の右手はオクターブな上に弱音なので、ペダル無しで楽譜通りに弾くのはかなり難易度高いと思う。

楽譜引用はエキエル版

コメントを書き込む

rust-jackのサンプルを見ると、


        put_p.write(&RawMidi {
                time: 0,
                bytes: &[0b10010000 /* Note On, channel 1 */,
                         0b01000000 /* Key number */, 0b01111111 /* Velocity */],
            })
            .unwrap();
        put_p.write(&RawMidi {
                time: ps.n_frames() / 2,
                bytes: &[0b10000000 /* Note Off, channel 1 */,
                         0b01000000 /* Key number */, 0b01111111 /* Velocity */],
            })
            .unwrap();

RawMidiの第一パラメータに時間情報が指定されているので、ここを色々変更して試してみた。すると、どうも大きな値を指定するとプログラムが異常終了する。


thread '' panicked at 'called `Result::unwrap()` on an `Err` value: UnknownError', ../src/libcore/result.rs:799
stack backtrace:
Jack: JackClient::ClientNotify ref = 4 name = rust_jack_show_midi notify = 3
Jack: JackClient::kXRunCallback
   1:     0x556f8a7d8a0f - std::sys::backtrace::tracing::imp::write::h6f1d53a70916b90d
   2:     0x556f8a7db50d - std::panicking::default_hook::{{closure}}::h137e876f7d3b5850
   3:     0x556f8a7daa1a - std::panicking::default_hook::h0ac3811ec7cee78c
   4:     0x556f8a7dafb8 - std::panicking::rust_panic_with_hook::hc303199e04562edf
   5:     0x556f8a7dae52 - std::panicking::begin_panic::h6ed03353807cf54d
   6:     0x556f8a7dad90 - std::panicking::begin_panic_fmt::hc321cece241bb2f5
   7:     0x556f8a7dad11 - rust_begin_unwind
   8:     0x556f8a80ffef - core::panicking::panic_fmt::h27224b181f9f037f
   9:     0x556f8a7cadf2 - core::result::unwrap_failed::hf4a2bbe780b35bc4
                        at /buildslave/rust-buildbot/slave/stable-dist-rustc-linux/build/obj/../src/libcore/macros.rs:29
  10:     0x556f8a7c8ad0 - >::unwrap::h089470f17859500e
                        at /buildslave/rust-buildbot/slave/stable-dist-rustc-linux/build/obj/../src/libcore/result.rs:737
  11:     0x556f8a7ce894 - sample::main::{{closure}}::hf2f3cb9afdb66a80
                        at /home/shanai/rust/midi-sample/src/main.rs:34
  12:     0x556f8a7cd12f - ::process::h4cdfe5f6478477da
                        at /home/shanai/.cargo/registry/src/github.com-1ecc6299db9ec823/jack-0.2.2/src/callbacks.rs:181
  13:     0x556f8a7ccef2 - jack::callbacks::process::h6e653a5eccae8773
                        at /home/shanai/.cargo/registry/src/github.com-1ecc6299db9ec823/jack-0.2.2/src/callbacks.rs:215
  14:     0x7f379543fc1d - 
  15:     0x7f379543f057 - 
  16:     0x7f379545767f - 
  17:     0x7f37950136b9 - start_thread
  18:     0x7f3794b3382c - clone
  19:                0x0 - 
fatal runtime error: failed to initiate panic, error 5

二分法で限界を探してみると、1023までなら大丈夫で、1024以上だとエラーになる。Jackのサイトにプログラミング・モデル的な解説が無いので理解に時間がかかったが、どうやらこういうことらしい。

この設定で指定したフレーム/ピリオドに指定した値が限界らしい。rust-jackではコールバックを登録するが、このコールバックは、ここで指定したフレーム/ピリオドで指定された間隔で呼ばれ、コールバック内では1フレーム内の動作のみを指定可能なようだ。なので、時間指定にはこのフレーム/ピリオドの値未満しか指定できない。で、この値の単位なのだが、1/サンプルレート 秒になる。つまり、この例であれば1/44100秒。なので、1フレームは1024/44100秒になる。分解能は1/44100秒なので、仮にテンポ=600(1分で四分音符600回演奏、つまり1秒に四分音符10回演奏)を上限とすると、四分音符の分解能は4410となる。MIDIシーケンサとしては十分な値だろう。

最後にピリオド/バッファーだが、ここで指定した個数のフレーム用バッファーが用意されるようだ。ここの値が大きければ事前にデータを準備しておけるので、コールバックでの処理が少々遅延したとしても途切れずに再生できるが、先にデータを用意しておかないといけないので、レイテンシーが増大する。この画面の右下にあるレイテンシーがそれを表しているようだ(1024 * 3 * 1/44100 = 0.0696598639)。

コメントを書き込む

Linux上でのMIDIは、今はjackを使うのが良さそうなので、ちょっと試してみることにする(Ubuntu 16.04)。使用したのはRolandのUM-1で、aplaymidiでは以下のように表示されて認識されていることが確認できる。


$ aplaymidi -l
 Port    Client name                      Port name
 14:0    Midi Through                     Midi Through Port-0
 16:0    UM-1                             UM-1 MIDI 1

jackのインストールは、


$ sudo apt install jack

インストールすると、qjackctlというアプリケーションがインストールされるので起動する。

接続というボタンを押してから、MIDIタブで、MIDIデバイス、アプリケーション同士を接続できるはずなのだが、ここにUM-1が表示されない。

かわりに、ALSAタブにUM-1が表示されている。

jackのドキュメント見ると、ALSAで認識されるデバイスは全て使えるよと書いてあって、なぜ認識されないのか散々悩んだのだが、どうもこれはMIDIには適用されないようだ。それでALSAで認識されたMIDIを、jack側にブリッジしてやる必要がある。といっても、単にa2jmididというブリッジアプリケーションを上げておけば良いようだ。インストールして


$ sudo apt install a2jmidid

あとは実行してやる(この時に-eオプションを付けないと、うまくいかない)。


$ a2jmidid -e
JACK MIDI <-> ALSA sequencer MIDI bridge, version 8 (7383d268c4bfe85df9f10df6351677659211d1ca) built on Thu Jan  1 09:00:00 1970
Copyright 2006,2007 Dmitry S. Baikov
Copyright 2007,2008,2009,2011,2012 Nedko Arnaudov

Bridge starting...
Using JACK server 'default'
Hardware ports will be exported.
Bridge started
Press ctrl-c to stop the bridge
port created: Midi Through [14] (capture): Midi Through Port-0
port created: Midi Through [14] (playback): Midi Through Port-0
port created: UM-1 [16] (capture): UM-1 MIDI 1
port created: UM-1 [16] (playback): UM-1 MIDI 1

再度Qjackctlで接続ボタンを押してみると...

UM-1には、INとOUT端子しかないのだが、どうも回路的にはMIDI throughもあるようで、これが一緒に認識されている。ここにつないでも意味が無いので注意が必要だ(起動オプションの-eを付けないと、このMIDI throughの方しかブリッジされなくて、これまたしばらく悩んだ)。

Update: 実際はブリッジ無しでも利用可能なことが分かった。

コメントを書き込む

前回の記事で、a2jmididを使ってブリッジしないと、jackでMIDIが利用できないと書いたが、私の勘違いだったようだ。qjackctlでalsaを使う設定をする時なのだが、

インターフェースのところに、MIDIだとMIDIデバイス(UM-1)を指定したくなるのだが、これではダメで、aplay -lで表示されるデバイスを指定する必要がある。


$ aplay -l
**** ハードウェアデバイス PLAYBACK のリスト ****
カード 2: PCH [HDA Intel PCH], デバイス 0: ALC887-VD Analog [ALC887-VD Analog]
  サブデバイス: 0/1
  サブデバイス #0: subdevice #0
カード 2: PCH [HDA Intel PCH], デバイス 1: ALC887-VD Digital [ALC887-VD Digital]
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0

大事なのは、MIDIドライバーと書かれた項目で、ここにseq/rawを指定する必要がある。このseq/rawの違いは、ここの"Currently the two MIDI routing systems coexist, and this leads to some confusion."の部分が詳しい。かいつまむと、ALSA API経由でMIDI使うことが無いのなら、rawでok(rawだと、jackがデバイスを排他オープンするので、ALSA経由で利用できなくなる)。そうでなければseqを選ぶ。互換性的にはseqを指定しておく方が良さそう。こうしておけば、接続設定の中でMIDIのタブに表示されるようになるので、a2jmididは不要になる。

コメントを書き込む
#132
2017/01/15 03:02

RustからMIDIを制御する方法を調査。ここに幾つか上がっている。ざっくり見た感じは、rmidは標準MIDIファイルを読み書きするためのもの。残りのうちjack以外は低レベルインターフェイスのみで、アドホックにMIDIメッセージを送受信できるだけのようだ。LinuxはリアルタイムOSではないので、これらを使って音楽を演奏するのは苦しそうだ。jackは発音タイミングを渡せるようなので、これを使うのが良さそう。サンプルが置いてあったので、これを試してみる。

cargo newでcrateを作って、Cargo.tomlに依存を追加する。


[dependencies]
jack = "^0"

あとは、サンプルを、src/main.rsに保存して、cargo runで実行。キー入力待ちになるので、Qjackctlで見ると表示される。

rust_midi_makerと、a2jのUM-1を選択した状態にして接続ボタンを押すと接続できる。

これで、うまくMIDIメッセージが送信されることが確認できたので、RustでMIDIシーケンサを実装するための事前の技術検証終了。

コメントを書き込む
#130
2017/01/13 11:41

gtk-rs研究の続き。今回は、画像を重ねてみる。やりたいことは、こういう画像と:

こういう画像とを重ねて:

こういう表示をしたい。

複数の画像の描画は、前回の記事の方法でも行える。


drawingArea.connect_draw(move |widget, context| {
    context.set_source_pixbuf(&pix1, 0f64, 0f64);
    context.paint();
    context.set_source_pixbuf(&pix2, 0f64, 0f64);
    context.paint();
    return Inhibit(false);
});

こんな感じで複数のPixbufを、set_source_pixbuf()してpaint()していけば良い。しかし、これだとdrawの度に描画が起きるため、数が増えていくとパフォーマンスに影響したり、ちらついたりするだろう。GTK+では、こういう時はSurfaceを使うようだ。


// Pixbuf(ト、ヘ音記号部分)
let clefpix = match Pixbuf::new_from_file("images/clefsImage.gif") {
    Err(e) => {
        println!("Err: {}.", e);
        return;
    }
    Ok(p) => p
};
// Pixbuf(#記号)
let sharppix = match Pixbuf::new_from_file("images/sharp.gif") {
    Err(e) => {
        println!("Err: {}.", e);
        return;
    }
    Ok(p) => p
};

// オフスクリーンバッファとして、ImageSurfaceを用意。
let isf = ImageSurface::create(Format::Rgb24, clefpix.get_width(), clefpix.get_height());

// Contextを生成して、
let ctx = Context::new(&isf);

// 描画
ctx.set_source_pixbuf(&clefpix, 0f64, 0f64);
ctx.paint();
ctx.set_source_pixbuf(&sharppix, 100f64, 100f64);
ctx.paint();

// 最後にflush()が必要のようだ。
isf.flush();

これで描画済のImageSurfaceが出来あがるので、drawの中では、これを描画すれば良い(ws.surfaceが上で作ったImageSurface)。


let ws = windowState.clone();
windowState.drawingArea.connect_draw(move |widget, context| {
    context.set_source_surface(&ws.surface, 0f64, 0f64);
    context.paint();
    return Inhibit(false);
});

できた。

サンプルコード全体は、GitHubに置いておいた。

コメントを書き込む
#129
2017/01/13 02:54

GTK+のRustバインディングである、gtk-rsを研究中。

アプリケーションから自由に描画するには、DrawingAreaという部品を使うようだ。ここにイメージを表示する方法を調査。

まず画像ファイルを読むには、Pixbufを使うのが良いようだ。


let pix = match Pixbuf::new_from_file("images/moomoo.jpg") {
    Err(e) => {
        println!("Err: {}.", e);
        return;
    }
    Ok(p) => p
};

DrawingAreaへの描画は、connect_draw()を使って、クロージャを登録する。


drawingArea.connect_draw(move |widget, context| {
...
});

このcontextが、cairo::Contextなので、これを利用して描画すれば良いのだが、APIを見た感じ、pixbufを描画するメソッドが見つからない。

途方に暮れていたら、gdk::prelude::ContextExtを見つけた。Rustのtraitは非侵入的に拡張できてしまうので、普通にAPIを眺めていてもダメで、APIサイトの検索で探さないとダメだ。


extern crate gdk;
use gtk::prelude::*;
...
drawingArea.connect_draw(move |widget, context| {
    context.set_source_pixbuf(&ws.pix, 0f64, 0f64);
    context.paint();
    return Inhibit(false);
});

できた。

サンプルコード全体は、GitHubに置いておいた。

コメントを書き込む
1 / 2