M5STACK/ESP32 で PSG 系音源をエミュレート

勉強会で見せていただいて気になっていた M5STACK を買ってみました。  🙂

内蔵スピーカーもついていますので hello world がてら、 PSG 音源をエミュレーションしてみようと始めたところ、なかなか鳴ってくれなくて苦労しました…

勢いがあるうちに忘れないようメモしておきます。

ソースは github で公開しています。(力尽きたので不出来、手抜きの部分は大目に見てください)

https://github.com/h1romas4/m5stack-synth-emulation

PSG(SN76496) VGM player on ESP32/M5STACK

開発環境

PSG エミュレーターを移植するのに gcc ツールチェインを使いたかったので開発環境は(Arduino ではなく) ESP-IDF を使いました。 リンク先の通りに SDK ファイルを展開して、 $IDF_PATH を設定してあげれば OK です。

今回は Linux を使ってやりましたが、Windows 10 の場合は WSL を使うと楽かもしれません。(未確認)

M5STACK ESP-IDF 用の雛形が github に提供されていますので、手順の通り git clone して make menuconfig してシリアルを設定して、make flash monitor すれば hello world できると思います。

https://github.com/m5stack/M5Stack-IDF
To use as a M5Stack component of ESP-IDF

ちなみに monitor の抜け方は ctrl + [ です。

自分がやったときは Makefile にバグがありいきなり、make flash できないという不具合に遭遇しています。

Detected overlap at address: 0xe000 for file:

これは Makefile にパッチする次のワークアラウンドで解消できました。(リポジトリは既に修正済みかもしれません)

(esp-idf) make flash fails due to the overlapping of offset addresses #1724

https://github.com/espressif/arduino-esp32/issues/1724

static heap の上限

static heap をたくさん使おうとすると

region `dram0_0_seg' overflowed by 67900 bytes

とリンクエラーになります。 がびーん。

どうやら static heap にサイズ上限が決められているようなので、static で取得するのをやめて動的に heap_caps_malloc() して回避しました。

build/ 下に app-template.map というファイルができていて、static heap のサイズが分かります。(この場合 DECAY_TO_ATTACK 変数に 0x4000 取られています)

.bss._ZL15DECAY_TO_ATTACK
0x000000003ffbd1cc     0x4000 /home/..../libsynth.a(ym2612.o)

また、make size すると次のように現在のメモリーサイズが表示できます。

Total sizes:
 DRAM .data size:    6488 bytes
 DRAM .bss  size:  118632 bytes
Used static DRAM:  125120 bytes (  55616 available, 69.2% used)
Used static IRAM:   36140 bytes (  94932 available, 27.6% used)
      Flash code:  181978 bytes
    Flash rodata:   64104 bytes
Total image size:~ 288710 bytes (.bin may be padded larger)

static heap を回避しても malloc で失敗することもありますので、あちこち移動させてうまいことメモリーに載せていきます。 なお、heap_caps_get_free_size() で動作中の残りメモリーを知ることができるようです。

内蔵 DAC の鳴らし方

なかなか鳴らなくて PSG エミュレーションが悪いのか DAC の操作が悪いのかで苦労しました…。

DAC は次のようなイメージで鳴ると思います。

i2s_config_t i2s_config = {
    .mode = static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
    .sample_rate = SAMPLING_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = static_cast<i2s_comm_format_t>(I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = 0,
    .dma_buf_count = 16,
    .dma_buf_len = 512,
    .use_apll = false,
    .fixed_mclk = 0
};

i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
i2s_set_pin(i2s_num, NULL);

size_t bytes_written = 0;
int16_t buf[2];
buf[0] = 0x0000; // left sample
buf[1] = 0x0000; // right sample
i2s_write((i2s_port_t)i2s_num, buf, sizeof(int16_t) * 2, &bytes_written, portMAX_DELAY);

buf には符号付きリトルエンディアンのステレオサンプリングを渡します。サンプルは 1フレームですが、この config で 1600byte くらい一気に渡せました。

flash への任意データーの書き込み

partitions.csv ファイルをつくり make menuconfig (sdkconfig) で指定してあげることにより、内蔵の flash メモリーの任意の位置に esptool.py でバイナリを書き込むことができます。

esptool.py --chip esp32 --port "/dev/ttyUSB0" --baud 115200 write_flash -fs 4MB 0x211000 "バイナリファイル"

プログラムから読み込むときは、

esp_partition_find_first() して esp_partition_mmap() でポインターがもらえます。便利。 🙂

その他

  • 普通に printf すると make flash monitor するだけで実行してコンソール出力されるので嬉しい。
  • プログラムが大きくなったせいか途中から “Make error of dangerous relocation” と怒られてリンクできなくなりました。 CFLAGS に -mlongcalls をつけることで解消しました。
  • M5STACK の内蔵 DAC は音が割れます。 PSG くらいがちょうど良さそうです。
  • (実はがんばって FM音源も移植したのですが、いい音で鳴りませんでした… ソースには残してありますので、気になる方は鳴らしてみてください…)

以前から充電できるポケステがあったらいいのになぁと思っていたのですが、ちょうどでてきてくれた M5STACK さん。 久しぶりに夢中になってしまいました。

今回で一通りの困りそうなことは踏んだ気がしますので、引き続き何か作っていきたいと思います。 🙂

Arduino で YAMAHA YM2151 を VGM ファイルで演奏させる

1990年代の多くのアーケードゲーム機に搭載されていた音源チップ YAMAHA YM2151 を Arduino からこの手でどうしても発声させてみたい。

電子工作を始めて Lチカもそこそこに製作を始めた”未来ガジェット”のひとつです。

結構前につくったものでしたが、どのように製作したか忘れぬよう github に Arduino スケッチと参考させていただいた資料・回路図のリンクを掲載してみました。

Play back the VGM format file with Arduino. (only YM2151)

https://github.com/h1romas4/arduino-vgmplayer

本スケッチでは VGM Format と呼ばれる、音源チップに流すデーターダンプ形式を、そのまま YM2151 に write して発声させる方式を採っています。

このことから Arduino のスケッチは 100行ほどの単純なものになり、まずは発声させてみたい方にはちょうど良い規模感のプログラムになっています。たとえば PSG を同時に鳴らしたい、なんて時も数行足してもらえれば実装できると思います。

演奏データとなる VGM ファイルは mml2vgm を使わせて頂き MML (Music Macro Language)で作成しました。MML の書き方は体が覚えていることでしょう 😀

https://github.com/kuma4649/mml2vgm

[概要]
このツールは、ユーザーが作成したMMLファイルを元にVGM/XGMファイルを作成します。

[機能、特徴]
[VGM]
・メガドライブ2台分の音源構成(YM2612 + SN76489 + RF5C164)*2にそったVGMを生成します。
(他にYM2151,YM2203,YM2608,YM2610B,SegaPCM,HuC6280に対応しています。)

github 上のサンプルは次のようにしてみました。(カエルの歌の3声輪唱です)

assets/vgmsaple.gwi

'@ M 110
   AR  DR  SR  RR  SL  TL  KS  ML  DT1 DT2 AME
'@ 022,005,000,004,005,041,000,001,005,000,000
'@ 016,008,008,008,002,000,001,002,005,000,000
'@ 031,018,000,004,010,044,000,008,009,000,000
'@ 031,009,007,008,002,003,001,001,009,000,000
   ALG FB
'@ 004,007

'X6 T160@110l4 r1r1r1r1 cdefedcrefgagfercrcrcrcrc8c8d8d8e8e8f8f8edc
'X7 T160@110l4 r1r1     cdefedcrefgagfercrcrcrcrc8c8d8d8e8e8f8f8edc
'X8 T160@110l4          cdefedcrefgagfercrcrcrcrc8c8d8d8e8e8f8f8edc

このテキストファイルを mml2vgm により .vgm 化して、Arduino のスケッチから PROGMEM で Flash メモリーに書き込んでいます。(music/vgmsample.h

曲の大きさによっては Arduino の 32KByte Flash に載りきらないため、作例では EEPROM を接続し書き込んだデーターを演奏させることもしています。(ちなみに EEPROM を単純に 1Byte 読みすると速度が追いつかなかったため、先読みのリングバッファのような実装を入れました)

ハードの方は Arduino UNO のシールドで実装しています。(まずはブレッドボードで動作を確認した方が良いと思います)

足の数に対して基板が結構小さく、おまけに全部ビニール線で繋いだため結構大変でした。。とはいえ、初心者の自分が、2日くらいハンダ付けをがんばれば完成しましたので、おそらくみなさまいけるかと思います。(部品は AliExpress で入手しています)

さて完成した機械から奏でられた音色は…

リーディングシュタイナー発動…!!!

苦労した甲斐もありまして、無事タイムリープに成功。 😀

子供の頃からゲームの音楽と言えば FM 音源で、なんとかいい音で聴こうと PC からラインを引っ張り出してステレオコンポに接続して、何度も何度も聴いていたことを思い出します。

この機械は、電源を入れればすぐ楽曲が奏でられますので「古の電子オルゴール」として現在も活躍中です。

Codenvy (Eclipse Che) で Spring Boot + Gradle ウェブプロジェクトを動作させる

ブラウザ版 Eclipse である Eclipse Che は、Docker を活用しさまざまな言語の開発環境をクリックひとつで構築でき、ブラウザから操作できる利点と合わせ「どこでも開発」が実現できるんじゃないかと思わせる、現在も活発に開発が進んでいる期待のプロジェクトです。 😀

今回、Eclipse Che の Saas である Codenvy を使って Spring Boot + Gradle ウェブプロジェクトを動作させるケースで、ハマりポイントや手間が結構ありましたので設定を記載したいと思います。

現在 Eclipse Che 上で Spring Boot + Gradle を動かそうとすると、いくつかの問題点があります。

  • Gradle (build.gradle) を解釈してくれないないため、ソースやクラスパスを通すことができない。 Eclipse でいうところの buildship 相当の実装がまだされていない。
  • IDE にインクリメンタルビルドが実装されてない(ですよね?)ため、Spring Boot DevTool のリロード機能を使うことができない。
  • Codenvy のリソースの問題か、ブラウザ上で動作する JS の問題か、結構フリーズする…

最後の問題はさておき、上の 2点は gradle を駆使してなんとかすることができましたので、Codenvy のログイン後からの手順を書いてみます。

Workspace の作成

プロジェクトを動作させるためのワークスペースを作成します。左メニュー Workspaces から Add を押下します。

SELECT STACK のフィルターで All を選択し、検索文字列に java debian を入力し “Java Debian” スタックを選択します。(2018/06/09 時点で CentOS を使っている Docker イメージを選択すると Workspace 開始がエラーになりましたので Debian にしています)

また、マシンのメモリーはデフォルト 2G になっていますが、3G まで無料で使えるようなので上げてしまってもいいかもしれません。

次に Spring Boot + Gradle (gradlew 入り)のソースコードを git から取得するため Add Project に git の Repository を指定します。

ここではサンプルで自分が公開している Spring Boot + Vue.js + Gradle + webpack のテンプレートプロジェクトを指定しています。

https://github.com/h1romas4/springboot-template-web.git

保存したら Create ボタンを押下してワークスペースを作成します。作成後、画面がワークスペースに遷移し Docker で環境が構築され起動し、git からリポジトリーからソースコードが取り込まれます。

build.gradle を修正し pom.xml を生成する

Eclipse Che は現在 build.gradle を認識できないため、このままであると .java はただのテキストファイル扱いで補完などができません。これを解決するため、Eclipse Che が扱える Maven の pom.xml を生成します。

Gradle に pom.xml を生成するプラグインがありますので、プロジェクトの build.gradle を次のように修正します。

maven プラグインを追加。(と maven プラグインが要求するため groud, version を追加)

plugins {
    id 'java'
    id 'eclipse'
    id 'maven'
    id 'com.moowork.node' version '1.2.0'
    id 'org.springframework.boot' version '1.5.9.RELEASE'
}

group = 'com.example'
version = '1.0-SNAPSHOT'

次に、dependencies に使うバージョンを追記します。(Gradle の Spring Boot プラグインは指定したプラグインのバージョンから、依存関係のライブラリバージョンも自動で認識してくれますが、Maven プラグインからは参照できないため)

また合わせて、gradle build 時に pom.xml が自動的に生成されるようにタスクを追加します。(task createPom 以降)

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web:1.5.9.RELEASE')
    compile('org.thymeleaf:thymeleaf-spring4:3.0.2.RELEASE')
    compile("org.springframework.boot:spring-boot-devtools:1.5.9.RELEASE")
}

task createPom {
    description "Generates a pom.xml in the project root directory; useful e.g. for IDEs which can read POM but don't directly support Gradle."
    doLast {
        pom {
        }.writeTo("pom.xml")
    }
}

compileJava.dependsOn createPom

build.gradle の修正が終わったら、画面下部の Terminal から次のコマンドを入力し初期ビルドします。

$ cd springboot-template-web/
$ ./gradlew build
$ mvn package
$ ./gradlew build

mvn package の後に再度 ./gradlew build しているのは”おまじない”です。(pom.xml に刺激を与えることで Eclipse Che が Maven プロジェクトとして認識してくれるようです。この手順を確立するまでハマりました。。)

Eclipse Che が pom.xml を認識するとプロジェクトのツリーに External Libraries として無事 dependencies が入るはずです。

.java も Java ソースファイルとして扱ってくれ、補完なども効くようになります。

アプリケーションの起動

Spring Boot のウェブアプリケーションを起動するために実行構成を作成します。

Create new command から bootRun タスクを次のように作成します。

Command Line

cd ${current.project.path} && ./gradlew bootRun

Preview URL

${server.8080}

なお、それぞれの入力部分はマルチラインになっていますが、必ず 1行で書いてください。空行でも改行コードが混在すると Windows(のブラウザ)では動かなくなります。どうやら LF スプリットしているらしく Linux なら動作してしまうという…(ハマり2)

Save 後 RUN ボタンでアプリケーションが起動します。

タスクのビューの preview: 欄にある URL をクリックするとポート 8080 で起動したアプリケーションにブラウザから接続でき、実行を確認することができます。

なお、アプリの停止は画面上部右の「■」から行えます。

インクリメンタルビルドとリロード

この Spring Boot プロジェクトには Spring DevTool の依存関係が入っており、Eclipse ではソースを修正すると自動的にアプリケーションが再起動し、ビルドアンド確認が簡単にできるように設定されています。

残念ながら Eclipse Che はソース修正後に自動的にコンパイルは走らないようなので、bootRun しているとは別の gradle を使って、ウォッチとインクリメンタルビルドをしてあげます。

Terminal から次のコマンドを入力します。

./gradlew build --continuous

これでファイルウォッチに入りますので、.java ファイル修正でアプリケーションが自動的に再起動されるはずです。(停止は Ctrl + d です)

webpack ビルド

このプロジェクトは webpack/nodejs のビルドを使い、JavaScript を ES2015 としたり CommonJS を取り入れたりしています。 nodejs のランタイムは Gradle のプラグインにより自動的にダウンロードされるようになっていますので、上部メニューの Run から Terminal で新しいターミナルを開き次のコマンドを入力するとビルドがかかります。

./gradlew webpack

また、JavaScript の修正からの自動ビルドをするには次のコマンドを入力するとウォッチモードに入ります。

./gradlew watch

詳しくは「フロントエンド技術を導入した Java ウェブアプリケーション開発」をご覧ください。

さて、Eclipse Che にて .js を開くと…

おっ(ΦдΦ)!!という感じになりましたが、自分の環境ではこの後ブラウザが何度やってもフリーズしてしまいました。。いい感じに動きそうなだけに、無念。。

最後に

実際に Codenvy で Eclipse Che を使ってみるとフリーズも多くまだまだこれからといった印象ですが、今後はエディタ部が Eclipse Orion ベースのものから VS Code でおなじみ Monaco Editor になるなど改良が進んでいくようです。

個人的にはブラウザから使うより、Eclipse Che の REST API を活用して任意のクライアントから Docker 環境や language-server に接続できる方向にも発展してくれると嬉しいですが、継続してウォッチしていきたいと思います。

とりあえず、本手順でいろいろ検証できると思いますので、余暇にでもお試しください。 🙂