るいもの戯れ言

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に置いておいた。

コメントを書き込む

Playframeworkのテンプレートエンジン、Twirlは、XMLも生成できるようだ。通常はXXX.scala.htmlといったファイル名にするが、これをXXX.scala.xmlとすれば良い。

ただ、デフォルトだとXML宣言を書いてくれない。これはWritableを実装してやれば良い。


  val xmlWriteable = new Writeable[Xml](
    xml => ByteString("""<?xml version="1.0" encoding="UTF-8"?>""" + xml.toString, ByteString.UTF_8),
    Some("text/xml")
  )

あとはコントローラで書き出す時に、このWritableを指定してやる。


      Ok(
        views.xml.atom(recs, settings)
      )(xmlWriteable)

コメントを書き込む

Playframework用のメール・プラグインの最新版で、以下のようなエラーでメールが送信できない。


org.apache.commons.mail.EmailException: Sending the email to the following server failed : smtp.gmail.com:587
        at org.apache.commons.mail.Email.sendMimeMessage(Email.java:1421)
        at org.apache.commons.mail.Email.send(Email.java:1448)
        at play.api.libs.mailer.SMTPMailer$$anon$2.send(MailerPlugin.scala:100)
        at play.api.libs.mailer.CommonsMailer.send(MailerPlugin.scala:130)
        at play.api.libs.mailer.SMTPMailer.send(MailerPlugin.scala:110)
        at play.api.libs.mailer.SMTPDynamicMailer.send(MailerPlugin.scala:117)
        at controllers.HomeController$$anonfun$postComment$1$$anonfun$apply$7$$anonfun$apply$1.apply$mcV$sp(HomeController.scala:101)
        at akka.actor.Scheduler$$anon$4.run(Scheduler.scala:126)
        at scala.concurrent.impl.ExecutionContextImpl$AdaptedForkJoinTask.exec(ExecutionContextImpl.scala:121)
        at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
        at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
        at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
        at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Caused by: javax.mail.MessagingException: Could not connect to SMTP host: smtp.gmail.com, port: 587;
  nested exception is:
        javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
        at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:2055)
        at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:697)
        at javax.mail.Service.connect(Service.java:386)
        at javax.mail.Service.connect(Service.java:245)
        at javax.mail.Service.connect(Service.java:194)
        at javax.mail.Transport.send0(Transport.java:253)
        at javax.mail.Transport.send(Transport.java:124)
        at org.apache.commons.mail.Email.sendMimeMessage(Email.java:1411)
        ... 12 more
Caused by: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
        at sun.security.ssl.InputRecord.handleUnknownRecord(InputRecord.java:710)
        at sun.security.ssl.InputRecord.read(InputRecord.java:527)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:973)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
        at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:543)
        at com.sun.mail.util.SocketFetcher.createSocket(SocketFetcher.java:348)
        at com.sun.mail.util.SocketFetcher.getSocket(SocketFetcher.java:215)
        at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:2019)
        ... 19 more

設定(application.conf)は、以下のようにしていた(これで以前は送信できていた)。


play.mailer {
  host = "smtp.gmail.com"
  port = 587
  ssl = yes
  user = "Set your gmail account(xxx@gmail.com)"
  password = "Set your gmail password"
#  debug=yes
}

現在のバージョンで、Gmailに送る場合は以下のようにする必要があるようだ(ssl=no, tls=yes)。


play.mailer {
  host = "smtp.gmail.com"
  port = 587
  ssl = no
  tls = yes
  user = "Set your gmail account(xxx@gmail.com)"
  password = "Set your gmail password"
#  debug=yes
}

コメントを書き込む
#1
2017/01/04 07:45

ずっと懸案だった古いjbossサーバを止めるため、年末の休みはブログ・サーバを作成。とにかく最低限の機能しかないけど、Angular + Playframeworkで作成。本当は1/3中になんとかしたかったけど間に合わなかった...

足りないコンテンツは、少しずつ移行して、1月中にはjbossを止めたいな。

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