1954

Thoughts, stories and ideas.

Arduinoで電子オーボエを作る

はじめに

3月の終わりに、響け!ユーフォニアム1期〜2期, リズと青い鳥, 誓いのフィナーレを一気見して「特別になりたい」気持ちを再確認し、オーボエを始めようと思い立ちました。

「昨今みんな在宅環境に投資してるし、いいイス買うようなものだし」と謎の論理を免罪符にしてMarigaux Lemaireの中古美品を買ったはいいものの、自宅は防音では無いので気軽に音は出せず、スタジオに練習に行くわけにもいきません。

自分はトランペットを長いことやっていますが木管楽器はリコーダーを除けば初めてで、オーボエの複雑な運指は問題でした。(トランペットはボタンが3つのみで音程の規則性も明確なので運指は簡単です。対してオーボエ(Lemaire)は22個キーがあり、音域によってはほぼ運指に規則性が無く丸暗記を必要とする上、左手人差し指ではハーフオープンを含め3つの状態を管理しなくてはなりません。)

今の練習状況では「第三楽章『愛ゆえの決断』」のオーボエソロを吹けるようになるまで何年かかるかわかりません。

そこで、家で手軽に運指を練習できるソリューションとしての電子オーボエの検討を始めました。

既存製品

ざっと調べた感じでは「電子オーボエ」そのものズバリな製品は見当たりませんでした。

AKAI EWIではオーボエに似た運指に切り替えられますが、そもそもキーの数も配置も異なるため再現度には限界がありそうです。

PCキーボードにオーボエのキーをマッピングする手も考えましたが、やってもかなり無理矢理な配置になるし、N-Key Roll Overに対応したキーボードを持っていませんでした。

DIY

したがって自前で製作することにしました。

おおまかに考えて、以下のような構成で電子オーボエが作れるはずです。

以下、製作の過程を紹介していきます。

作った3DモデルやArduinoスケッチはこちらで公開しています。

github.com

ブレスコントローラー

ArduinoでUSB MIDIブレスコントローラーを作る、まさに求めていたことをやっている先例がありました。

hackaday.io

同型番のセンサー(MPVZ4006GW7U)をDigiKeyで注文し、Melodion 唄口も入手してブレッドボードで動作確認します。

以下のように直でArduinoとセンサーをつなぎ、

MPVZ4006GW7U Arduino Uno R3
Vs 5V
Gnd GND
Vout A0

スケッチを書き込んでシリアルモニターで確認します。

int intensity;
int reading;
int sensorIni;

void setup() {
    delay(200);
    Serial.begin(9600);
    sensorIni = analogRead(A0);
}

void loop() {
    reading = constrain(analogRead(A0) - sensorIni, 0, 1014 - sensorIni);
    intensity = map(reading, 0, 1014 - sensorIni, 0, 127);
    Serial.println(intensity);
}

f:id:ocadaruma:20200505102347p:plain

記事でOptionalとして言及されていたdecoupling circuitは確かに省略しても動作に問題なさそうだったので、今回は無しで行きます。

筐体設計

電子オーボエの筐体をどんな形にするか、実際のオーボエを参考に穴の位置を決めつつ、ざっくり考えていきます。

f:id:ocadaruma:20200505111541p:plain

もっと本物のオーボエのような見た目にできればかっこいいでしょうが、造形が難しそうなのでこのへんを落とし所とします。

完全に直方体だとキーを押さえるのに邪魔になるため、若干流線型にしています。

3Dモデル作成

Blenderで作っていきます。。

f:id:ocadaruma:20200505113901p:plain

ブレスコントローラーやArduinoの取り付け位置まで緻密に設計していると無限に時間がかかりそうだったので、ある程度の配置だけイメージしつつ、残りは後で糸ノコで調整する前提で出力してしまいます。

3Dモデル出力

DMM.makeを利用しました。

素材は、キャンペーンで安くなっていたのでPA12(MJF)を選択。

上部と底部の2モデル合わせて15,000円でした。サイズ大きいので結構かかりますね。。

もっと小さくパーツ分けてあとでつなぎ合わせるほうがいいのかな。そうすれば家庭用プリンターでも出力できそうです。(持ってないけど)

筐体加工

無事出力できたはいいのですが、なんと第二オクターブキーをはめる穴を開け忘れていました。。ここは糸ノコで開けます。

f:id:ocadaruma:20200505120623p:plain

また、以下はモデル出力時点ではTBDにしていたので、寸法を図りつつ加工していきます。

  • 上部と底部をナットで止めるための穴
  • Arduinoを固定するための穴
  • ブレスセンサーを配置したユニバーサル基板を固定するための穴
  • 唄口を露出させるための穴

キースイッチとPCB配置

キースイッチをはめ込んで、そこに無限の可能性 アルタナを載っけていきます。

寸法通りに出力したので当たり前といえばそうですが、きれいにハマってちょっと感動。

f:id:ocadaruma:20200505123844p:plain

キーマトリックス設計

Arduino Uno R3のGPIOは14本で、オーボエのキーは23個(今回、左手人差し指ハーフオープンを別個のキーとして実装します)あるためダイレクト配線ではピンが足りません。 したがってキーマトリックスを組みます。

23個のキーに番号を振ったのち、

f:id:ocadaruma:20200505124202p:plain

キーマトリックスへ割り当てます。data[0-4]がINPUT_PULLUP、sel[0-4]がOUTPUTモードです。

任意キーの同時押しは必須要件なのですべてのキーにダイオードも入れます。

f:id:ocadaruma:20200505125635p:plain

実装

表面実装ダイオード1N4148Wを各無限の可能性にはんだ付けして、キーマトリックスを配線していきます。。

次に、ブレスコントローラーを取り付けます。

中身は最終的にこうなります。

f:id:ocadaruma:20200505131111p:plain

上部と底部を閉じてナットを締めればハードウェアの完成です。

f:id:ocadaruma:20200505130756p:plain

スケッチ実装

ロジック的に難しいことは無いですが、運指表を見ながら音との対応づけを根気よくコードに落としていきます。。

https://github.com/ocadaruma/roboe/blob/v0.1/oboe.cpp#L645

Pitch OboeKeys::toPitch() const {
    switch (this->bitFlags()) {
        case B_FLAT_3_0:
            return { 3, PitchName::B, Accidental::FLAT };
        case B_3_0:
            return { 3, PitchName::B, Accidental::NATURAL };
        case C_4_0:
            return PITCH_MIDDLE_C;
        case C_SHARP_4_0:
        case C_SHARP_4_TRILL_0:
....

C++11準拠でスケッチを書けて便利です。

ArduinoをUSB MIDI controller化

Serial.printデバッグである程度動作確認できたら、ArduinoMoco LUFAでUSB MIDI controller化していきます。

手順はこちらに詳しいです: Arduino Moco LUFA

Putting together

一通り出来上がったので手持ちのDAWで鳴らしてみます。

。。。おおむね問題ないのですが、タンギングが効きません。(タンギングとは、舌を歯やリードに当てて息の流れを遮断し、音を止めることです)

それはそうで、ブレスコントローラーは下記のように実装していました。

  • センサーはホース内の圧力に応じて値が変化する
    • 息を吹き込むとホース内の圧力が上がる
  • 唄口 -> ホース -> センサーは密閉されており、息の通り道は無い

舌をどこに当てたところで圧力が変化しないためタンギングができないのです。

そこで、唄口に小さな穴を開けます。

f:id:ocadaruma:20200505141722p:plain

これにより、継続的に息を吹き込んでいる限り圧力がかかってセンサーが反応するが、息を遮断すると穴から息が逃げて圧力が下がるようになり、タンギングが効くようになります。

というわけで最終的にできたものを演奏した動画がこちらです。(ビブラートは音源が勝手にかけてくれている。。)

youtu.be

まとめ

これで実際オーボエの運指の練習になるのかはさておき、ほぼ想定通りのハードウェアが出来上がりました。(なおこの記事ではすべてがスムーズにいったかのように書いていますが、実際は試行錯誤やパーツ到着のリードタイムなどもあり、着手から完成まで1ヶ月くらいかかってます)

ただし改善したい点もいくつか出てきました。

  • キーの配置を実際のオーボエにもっと近づけたい
    • 特に小指キーがかなり苦しい
    • 穴の配置を斜めにしたりすればマシになりそう
  • キーキャップを取り付けたい
    • キースイッチ同士をかなりタイトに配置したので普通のキーキャップがハマらない
    • そもそも、キーボード用キースイッチよりもっと適したスイッチがあるのでは?
  • 3Dモデルの完成度を上げて、印刷後の追加工作を不要にしたい
    • 糸ノコとかドリルは極力使いたくない
  • 専用PCBで配線の手間を減らしたい
  • 唄口は、衛生面から交換を容易にしたい
    • 手軽にはずして水で洗えるとうれしい。現状は唄口とホースがきつめに噛んでるので筐体のネジ外してホースを引っ張り出して押さえながらグリグリ抜く必要がある

次に作るとしたらこのあたりが課題です。

Gradle maven-publish pluginでSonatype OSSRHにlibraryをpublishする

Gradleでmaven repositoryにlibraryを上げる場合、maven pluginとmaven-publish pluginの二つの選択肢がある。

以前はmaven-publishがsigningをサポートしてなかったりでmaven pluginを使うケースが多かったようだが、現在はすでにstableになっている。

docs.gradle.org

use of the maven plugin is discouraged as it will eventually be deprecated — please migrate

と記載もあり、今後はmaven-publishを使うのがよいだろう。

Initial Setup

まずはこちらに従い、accountおよびticketを起票する。

central.sonatype.org

すでに済んでいる人は飛ばしてよい。

Setup credentials

ここもSonatypeのGuideに従い、$HOME/.gradle/gradle.propertiesにcredentialsを用意したものとする。

https://central.sonatype.org/pages/gradle.html#credentials

Setup build.gradle

ほとんどGradleのofficial documentを見ながら設定すればよいだけだが、maven pluginとの違いについて少し。

maven pluginではpomに署名するために以下のような記述が必要だった。

uploadArchives {
    repositories {
        mavenDeployer {
            beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
        }
    }
}

maven-publish pluginではMavenPublicationを通して色々な設定をしていくわけだが、signブロックを以下のように書けばpomも自動的に含まれるので特別な記述は必要無い。

signing {
    sign publishing.publications.mavenJava
}

Publish

maven-publish pluginではpublish taskを使う。

$ ./gradlew --no-daemon publish

local maven repositoryにpublishする場合は以下。

$ ./gradlew --no-daemon publishToMavenLocal

Conclusion

maven pluginを使っていたlibraryをmaven-publishに移行してみたが、とくに問題に当たらず公開できた。

完全なsampleは以下にある。

github.com

RedisのHyperLogLogの誤差について

HyperLogLogは集合のcardinalityを近似する確率的アルゴリズムです。

RedisにもPFxxxというcommandで実装されており、標準誤差は0.81%です。

The returned cardinality of the observed set is not exact, but approximated with a standard error of 0.81%.

redis.io

HyperLogLogは確率的アルゴリズムなので、精度は統計の手法で評価します。

HyperLogLogは期待値がcardinalityとなる確率変数で、標準偏差を真のcardinalityで割って得た相対誤差が0.81%であるということです。

ということは、誤差が0.81%を大きくはみ出すこともあるのでしょうか?

もちろんあります。

以下のデータをRedis 4.0.9にPFADDすると、真のcardinality 10000に対しPFCOUNTの値は1で、相対誤差-99.99%です。

gist.github.com

誤差が大きくなるような入力とは

HyperLogLogでは、ハッシュ関数(Redisの場合murmurhash2)を使ってランダムな試行をシミュレートします。

つまりハッシュ値が衝突するとき誤差が大きくなるのは自明ですが、一般にハッシュのpre imageを探すことは困難です。

いっぽう、HyperLogLogが保持するのはハッシュ値バケットの振り分けと、先頭で連続する0のbit数のみです。(くわしい話はぜひbuilderscon tokyo 2019で!)

つまり完全に衝突せずとも、同じバケットで同じ先頭の0 bit数のハッシュ値になる入力をさがせばよいことになります。

github.com

まとめ

builderscon tokyo 2019ではこういった誤差の話や動作原理も含め、HyperLogLogの色々な話をします。

builderscon.io

ビルコンにお越しの予定の方はぜひ聴きに来てみてください!

2018年の振り返り

登壇

LINE関連をメインに、いくつかのイベントで登壇の機会がありました。

仕事ではLINE Ads PlatformとりわけDMPの開発を担当していて、それについての発表が多かった。

LINE Ads Platformは8月に大幅刷新を行い、これからどんどんやっていくフェーズなので来年も楽しみです。

書いたもの

GoのGCについて調べた内容を、LINEの夏版Advent Calendar的なものに寄稿しました。

engineering.linecorp.com

ISUCON

メンバーは昨年に引き続き、前職の元同僚の方と組んだ「チーム人間性」で参戦しました。

昨年は予選で敗退してしまいましたが、今年は念願の本選出場を果たしました。

ocadaruma.hatenablog.com

とにかく運営の素晴らしさが際立っていました。

音楽

今年はセッションオフみたいなやつに行き始めてみました。

アイカツ、とかアニソンなんでも、とかテーマが決まっていて、やりたい曲に立候補し、当日スタジオで合わせるのが基本的な流れです。

イベントはおもにこのサイトやTwiplaで探しました。

バンオフ、やろう。 | バンオフまとめサイト「バンオフ.info」

参加したイベントリストは以下です。どの会もとにかく愛がすごかった。

来年

  • ベースをやっていく
    • トランペットよりもコンディションのコントロールがしやすくて精神的には楽。バテないし
    • 今までジャズのセッションはほとんどトランペットだったんですが、ベースでもぼちぼち参加したい
  • なんかすごいもの作る
    • めっちゃ面白い音ゲーを作る予定だったんですが作れなかったので
  • 引っ越す

複数サーバーでtail -fっぽく読むJavaライブラリtailer7

tailer7というJavaライブラリを公開しました。

github.com

これを使うと、たとえばcommons-io Tailerでログをtail -fしつつtailer7のLogSenderに投げれば、複数サーバーでLogTailerを立ち上げてstreamingでログを受け取れます。

イデアについてはこの記事にインスパイアされています。

lincolnloop.com

tailer7は以下のような用途を目的として作ったもので、厳密な順序保証とか信頼性とかそういった考慮はされていません。

  • Ansibleを実行するUIを開発していて、ansible logがリアルタイムに流れる画面を作りたい
    • リアルタイムに流れる画面を開き直したとき、新しく流れてくるログだけじゃなく、まずログの頭から読みたい
    • Ansibleを実行するサーバーはtailer7にログを投げ、画面を提供するサーバーはtailer7からログを読めば、スケールしやすくて便利!

ISUCON8本選に出場して5位だった

Webアプリケーションの高速化バトルISUCON8本選に「チーム人間性」で出場しました。

最終スコアは13,914で5位という結果に終わりましたが、素晴らしい運営と問題で、大変楽しいコンテストでした。

使用言語はGoでした。

やったこと

3人チームで参加し、自分はデプロイなどの足回りやインフラ周りを主に担当しました。

ここでは自分のやったことにフォーカスして書きますが、他にもクエリのキャッシュやmysqlのindex最適化など色々入ってます。

非docker化

今回与えられたマシンは4台で、ベンチマーク対象として指定できるのは1台でした。

初期状態では全てのマシンが同じ中身で、1台の中にnginx, mysql, アプリが全てdocker-composeで上がっていました。

後々辛くなりそうな気がしたので、dockerを剥がして以下のような構成にしました。

  • app server1
  • app server2
  • app server3 兼 nginx (エントリポイント)
  • db server (mysql8)

また、各サーバーのプロビジョニングはansibleで行いました。

ログのバッファリング

今回のアプリケーションには、外部のログ分析APIに一部の操作のログを送信するという仕様がありました。

ただしログ分析APIにはrate limitが設けられていて、rate limitを超えて送信した分はログが欠損し、ベンチマーク後のvalidationを通過できずfailするようになっています。

初期状態では1件ごとにログを送信しているため、スコアが伸びてくるとrate limitに引っかかってベンチマークがfailしてしまいます。

お誂え向きに、ログ分析APIにはバルクでログを受け付けるAPIが用意されていたので、goroutineとchannelを使ってログを一定数バッファリングしてバルクで送信するようにしました。

他の言語を使っていて、バッファリングをいい感じに書くのに苦労したチームもいたようです。

share有効化

今回は「仮想椅子取引所ISUCOINが世界的SNS"いすばた"と提携開始!」というシチュエーションで、いすばたでシェアされるとバイラル的にユーザーが増え、スコアが伸びるようになっています。

ただし、すごい勢いでユーザーが増える(ような挙動をベンチマーカーがする)ことによりrequest timeoutが頻発して、規定のエラー数を超えてfailしてしまうため、初期状態ではシェア機能がOFFになっています。

これを、10%のリクエストでのみシェア機能を有効にするようにしました。(単純にシェア機能を有効にするだけだと、エラー数超過でfailしてしまった。)

シェア機能を有効にするリクエストを絞るという発想にいたったのが17:30ごろだったのでだいぶ苦し紛れでしたが、これによりスコアがだいぶ伸びました。

まとめ

スコアの遷移はこんな感じでした。(予選もそうでしたが、ポータルサイトかっこよかった)

f:id:ocadaruma:20181021003417p:plain

うまく分担して進められた気はするものの、それでも時間が足りないと感じるほどボリュームのある問題でした。

あらためて、素晴らしい運営をしてくださったみなさん、ほんとうにありがとうございました!!

redshift-fake-driverでAmazon Redshiftをモックする

Amazon Redshiftは便利でコスパのよいDWHですが、時間に対する従量課金なので、ちょっとした動作確認のために立ち上げっぱなしにしとくのが気がひける場合もあります。

もしJVMプロジェクトを開発しているなら、redshift-fake-driverを使うことでRedshiftを代替できます。 github.com

What is redshift-fake-driver

Redshift固有のSQL syntaxを無視したり同等のものに置き換えてPostgresqlまたはH2にプロキシする、JDBCドライバーです。

UNLOAD, COPYのようなコマンドもサポートしています。

その場合、Java system propertyでS3エンドポイントを渡すことで、fake-s3のようなS3互換のモックを使うことも可能です。

Usage

では、fake-s3とPostgresqlを使って、使い方を見ていきます。

言語はScalaで、実行はAmmonite REPLで行います。

0. Postgresqlのセットアップ

$ createuser sample_user
$ createdb -O sample_user sample_database

1. fake-s3のセットアップ

$ gem install fakes3
$ fakes3 -r ./fakes3_root -p 9444

2. 依存の追加

redshift-fake-driverは、artfifactにAWS SDKPostgresql driverを含まないため、それらの依存を追加する必要があります。

以降、Ammonite REPLは立ち上げっぱなしと仮定します。

$ amm
scala> import $ivy.`com.amazonaws:aws-java-sdk-s3:1.11.43`
scala> import $ivy.`org.postgresql:postgresql:9.4.1211`

3. バケット作成

scala> import com.amazonaws.auth.BasicAWSCredentials
scala> import com.amazonaws.services.s3.{S3ClientOptions, AmazonS3Client}
scala> val options = S3ClientOptions.builder().setPathStyleAccess(true).build()
scala> val s3Client = new AmazonS3Client(new BasicAWSCredentials("DUMMY", "DUMMY"))
scala> s3Client.setEndpoint("http://localhost:9444/")
scala> s3Client.setS3ClientOptions(options)
scala> s3Client.createBucket("bar")

4. redshift-fake-driverのインストール

scala> import $ivy.`jp.ne.opt::redshift-fake-driver:1.0.7`

Scala以外のJVM言語の場合、artifact名はredshift-fake-driver_2.11などとすればよいです。

また今回fake-s3を使うため、Java system propertyを以下のように設定します。

scala> sys.props.put("fake.awsS3Endpoint", "http://localhost:9444/")
scala> sys.props.put("fake.awsS3Scheme", "s3://")

5. CREATE TABLE

Redshift固有の情報を含んだDDLでテーブルを作成します。

redshift-fake-driverを使うときは、JDBC接続文字列をjdbc:postgresqlredshiftで始めます。

scala> import java.util.Properties
scala> import java.sql._
scala> val url = "jdbc:postgresqlredshift://localhost:5432/sample_database"
scala> val prop = new Properties()
scala> prop.setProperty("driver", "jp.ne.opt.redshiftfake.postgres.FakePostgresqlDriver")
scala> prop.setProperty("user", "sample_user")
scala> Class.forName("jp.ne.opt.redshiftfake.postgres.FakePostgresqlDriver")
scala> val conn = DriverManager.getConnection(url, prop)
scala> val stmt = conn.createStatement()
scala> stmt.execute("""
               |CREATE TABLE foo_bar(a int ENCODE ZSTD, b varchar(255))
               |DISTSTYLE ALL
               |DISTKEY(a)
               |INTERLEAVED SORTKEY(a, b);
               |""".stripMargin)

ちゃんとテーブルが出来ています。

$ psql -U sample_user -d sample_database
sample_database=> \d foo_bar
           Table "public.foo_bar"
 Column |          Type          | Modifiers
--------+------------------------+-----------
 a      | integer                |
 b      | character varying(255) |

6. UNLOAD

まずデータを入れます。

$ psql -U sample_user -d sample_database
sample_database=> INSERT INTO foo_bar (a, b) VALUES (1, 'one');
sample_database=> INSERT INTO foo_bar (a, b) VALUES (2, 'two');

UNLOADを打って、fake-s3のデータを確認します。

scala> stmt.execute("""UNLOAD ('select * from foo_bar') to 's3://bar/result'
         |CREDENTIALS 'aws_access_key_id=DUMMY;aws_secret_access_key=DUMMY'
         |ADDQUOTES""".stripMargin)

ちゃんとデータが出来ていることがわかります。

f:id:ocadaruma:20180428190123p:plain