るいもの戯れ言

作品15も、9に引き続き3つで構成されている。作品15の最初の曲となるこの曲は、第3曲と同じで前後を穏かな長調、中間部を激しい短調のパッセージという形式になっている。ただ第3曲よりも洗練されて、全体に簡潔にまとめらている。

中間部、第2小節はペダルを踏む指示があるけど、スタカーティシモ。どのくらいペダルを踏み込むのかが演奏者に任されている感じ。

楽譜引用はエキエル版

コメントを書き込む

Rustで、Multimap(1つのキーに複数の値を登録できるMap)を実装してみる。後でキーをソート順の取り出したいので、BtreeMapを使って、値にVecを入れる。


use std::collections::BTreeMap;

struct Bag {
    map: BTreeMap<i32, Vec<i32>>
}

impl Bag {
    fn add(&mut self, key: i32, value: i32) {
        match self.map.get_mut(&key) {
            None => {
                self.map.insert(key, vec!(value));
            },
            Some(vec) => vec.push(value)
        }
    }
}

でも、これはコンパイルが通らない。


error[E0499]: cannot borrow `self.map` as mutable more than once at a time
  --> /Users/shanai/.emacs.d/rust-playground/at-2017-01-22-230525/snippet.rs:32:17
   |
30 |         match self.map.get_mut(&key) {
   |               -------- first mutable borrow occurs here
31 |             None => {
32 |                 self.map.insert(key, vec!(value));
   |                 ^^^^^^^^ second mutable borrow occurs here
...
35 |         }
   |         - first borrow ends here

mutable borrowが2回起きているという警告。まずここでself.mapに対するmutable参照を取得している。


        match self.map.get_mut(&key) {

で、このmatchが終了する前に、ここでもう一度self.mapに対するmutable参照を取得している(insertは、mutableなselfが必要)。


            None => {
                self.map.insert(key, vec!(value));
            },

つまりmutableな参照が、2つ存在してしまう瞬間がある。これはRustでは禁止されている。以下がエラーになるのと同じ。


    let mut i = 0;
    let pi = &mut i;
    let pi2 = &mut i;

スコープを分けて、mutable参照が2つ同時に存在する瞬間を無くしてしまえば良い。


impl Bag {
    fn add(&mut self, key: i32, value: i32) {
        {
            match self.map.get_mut(&key) {
                None => {},
                Some(vec) => {
                    vec.push(value);
                    return
                }
            }
        }
        
        self.map.insert(key, vec!(value));
    }
}

でも、BtreeMapにはentry()という素敵なメソッドがあるので、これを使うのが簡単。


impl Bag {
    fn add(&mut self, key: i32, value: i32) {
        self.map.entry(key).or_insert(vec!()).push(value)
    }
}

entryはキーを引数に取り、Entryというenumを返す。指定されたキーが無ければVacant、あればOccupiedが返る。or_insert()を呼び出すと、Vacantの場合は、引数に指定された値をmapに格納した上で、その値へのmutable参照を返す。Ocupiedの場合は、値へのmutable参照を返す。なので上のように書けば目的が達成できる。


fn main() {
    let mut bag = Bag { map: BTreeMap::new() };
    bag.add(1, 10);
    bag.add(1, 20);
    bag.add(2, 100);
    println!("map = {:?}", bag.map);
}


map = {1: [10, 20], 2: [100]}

コメントを書き込む

DrawingAreaは、デフォルトではマウスのイベントを拾えないようだ。発生するイベントの種類を増やすには、WidgetExt::add_event()というメソッドを使うらしい。


    let drawingArea: gtk::DrawingArea = builder.get_object("drawingarea1").unwrap();
    drawingArea.set_size_request(300, 300);
    drawingArea.add_events(gdk_sys::GDK_BUTTON_PRESS_MASK.bits() as i32);
    drawingArea.add_events(gdk_sys::GDK_BUTTON_RELEASE_MASK.bits() as i32);

JavaのSwingだと、clickedというイベントで同じ場所でマウスのボタンが押されて離されたという状態を取得できたが、どうやらGTK+には、そういうのが無いようだ。自分でPRESSの時の座標を覚えておいて、RELEASEの座標が同じだったらクリックと判定する必要がある模様。イベントハンドラの登録は、WidgetExt::connect_event()を使う。


        drawingArea.connect_event(|_, e| {
            let clone = e.clone();
            match e.get_event_type() {
                EventType::ButtonPress => {
                    let res: Result<EventButton, Event> = clone.downcast();
                    println!("pressed: {:?}", res.unwrap().get_position());
                },
                EventType::ButtonRelease => {
                    let res: Result<EventButton, Event> = clone.downcast();
                    println!("released: {:?}", res.unwrap().get_position());
                },
                _ => {}
            }
            return Inhibit(false);
        });

Event型で来るため、特定のイベントであるかをEventTypeで判定して、ダウンキャストするというなんとも汚ないコード。なぜイベント自体をenumにしなかったのかな。GTK+との関係で難しいのだろうか。

クロージャの引数は&Eventで、downcastの引数は、&selfではなくてselfそのものなので、そのままイベントを渡すとmoveが起きてしまってコンパイルエラーになる。なので、一度clone()している。

コードサンプルは、こちら

コメントを書き込む
ショパン・ノクターン第3曲 作品9-3

作品9は3つのノクターンで構成されていて、第3曲が最後。ショパンとしては、この曲は意欲作だったのではないかと思う。ショパンのノクターンは3部構成になっているものが多いけど、この曲は、その中間部の雰囲気をがらりと変えて大きな効果を得ることを狙ったのだと思う。ただ、残念ながら一般には作品9の中での知名度は最も低くなってしまった。まぁ普通の人からすると、え、何、なんでノクターンなのに、こんなに激しいの? みたいな違和感が大きかったんだろうな。まぁ、こんな具合に作曲者が空回りしてしまうケースというのは多い。

この曲は、スタカーティシモが多用されている。面白いのはタイの後の音にスタカーティシモが付いているケースがあって、これ、どう表現するか悩みどころ。実際の演奏会なら大げさに右手を跳ね上げたりするんだろうか。

中間部。Agitatoでガンガン行く。ショパン的には「どうだ!」って感じだったのだろうな。

最後のアルペジオは、ものすごく綺麗だけど、これppで弾くのすごく難しそうで禿げそう。

楽譜引用はエキエル版

コメントを書き込む

今回のお題は、2値(白黒)+alpha(透過付き)のgifファイルを読み込み、その中の黒を赤として描画したいというもの。要は「選択状態」の描画を赤色でしたい。だけど元の画像ファイルは2値(白黒)だよ。という状況。

Pixbufは、2値の画像読み込んだ場合でも、中ではRGBAそれぞれに8bitを割り当てているようだ。なので、ピクセル操作は思ったより簡単だった。


fn to_red(pixbuf: &Pixbuf) {
    assert!(
        pixbuf.get_colorspace() == gdk_pixbuf_sys::GDK_COLORSPACE_RGB,
        "Unsupported color space: {}", pixbuf.get_colorspace()
    );
    assert!(pixbuf.get_has_alpha(), "This image does not have alpha channel");
    let n_channels = pixbuf.get_n_channels();
    let w = pixbuf.get_width();
    let h = pixbuf.get_height();
    let rowstride = pixbuf.get_rowstride();
    let mut buf: &mut [u8] = unsafe {
        pixbuf.get_pixels()
    };

    for y in 0..h {
        for x in 0..w {
            let offset = (y * rowstride + x * n_channels) as usize;
            let r = buf[offset];
            let g = buf[offset + 1];
            let b = buf[offset + 2];
            let a = buf[offset + 3];
            if r == 0 && g == 0 && b == 0 {
                buf[offset] = 255;
            }
        }
    }
}

一応colorspaceと、alpha付きであることをチェック。


    assert!(
        pixbuf.get_colorspace() == gdk_pixbuf_sys::GDK_COLORSPACE_RGB,
        "Unsupported color space: {}", pixbuf.get_colorspace()
    );
    assert!(pixbuf.get_has_alpha(), "This image does not have alpha channel");

あと、注意としてパディングされている可能性があるので、rowstrideにyを掛けてやる必要があるようだ。get_pixelsは、&mut [u8]が返ってくるので、大胆にもそのまま中をいじれてしまう。RGB全てが0(黒)なら、Rだけ255にしてやる。


    for y in 0..h {
        for x in 0..w {
            let offset = (y * rowstride + x * n_channels) as usize;
            let r = buf[offset];
            let g = buf[offset + 1];
            let b = buf[offset + 2];
            let a = buf[offset + 3];
            if r == 0 && g == 0 && b == 0 {
                buf[offset] = 255;
            }
        }
    }

コードは、GuiHubに置いておいた。

コメントを書き込む
#199
2017/01/21 10:57

Rustで以下のようなJavaのenumを実現したい。


public enum SharpFlat {
    SHARP(1), DOUBLE_SHARP(2), NATURAL(0), NULL(0), FLAT(-1), DOUBLE_FLAT(-2);

    private final byte offset;

    private SharpFlat(int offset) {
        this.offset = (byte)offset;
    }

    public int getOffset() {
        return offset;
    }
}

RustのenumはJavaとかのenumとは考え方が違って直和型の定義のようだ

structで定義してもいいけど、enumになっていればexhaustive checkされるので、やはりenumの方がありがたい。試してみると、enumもtraitを実装できるので、以下のようにすることで解決できた。


enum SharpFlat {
    SHARP,
    FLAT,
}

trait HasOffset {
    fn offset(&self) -> i8;
}

impl HasOffset for SharpFlat {
    fn offset(&self) -> i8 {
        match *self {
            SharpFlat::SHARP => 1,
            SharpFlat::FLAT => -1,
        }
    }
}

fn foo(sharp_flat: Option<SharpFlat>) {
    match sharp_flat {
        None => {
            println!("none");
        },
        Some(SharpFlat::SHARP) => {
            println!("sharp {}", SharpFlat::SHARP.offset());
        },
        Some(SharpFlat::FLAT) => {
            println!("flat");
        },
    }
}

fn main() {
    foo(Some(SharpFlat::SHARP));
}

コメントを書き込む
旧サイト閉鎖。こちらにリダイレクトするようにしました。
コメントを書き込む

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

構成は単純だけど、楽譜上、作曲者の指示が非常に多く、その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をそのまま使うか、これで返ってきた領域の和集合を計算して、そこに対して再描画するのが良さそう。

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