rakushoo blog

ジャズとプログラミングがやりたい人

アドリブ本番にあると役立つアイディア

前回までに、アドリブ練習のためのツールを紹介しましたが、実際のセッションやコンボのライブで実践できるものにするのに一歩足りないと思い、いろいろ検討しています。

その中の一つのアイディアを紹介したいと思います。

アドリブする瞬間に考えていること

アドリブ練習ツールでは、コードの解析を自動化して譜面を表示することで、プレイヤーの負担を減らすような役目を果たしていました。 しかし、自分自身がアドリブの演奏に使うにはもう1,2ステップほど何かが足りないと感じていました。

実際に演奏する瞬間は、正直いうとコードやらスケールなどは意識できていません。その場で流れるベース・ピアノの音程を耳が捉えているのですが、思考せずに指が反応している感じです。「Bb7だからオルタードスケールだな」などと言語化しているとロスになります。スケールが分かったとしてもスケール→音階を思い出す→運指を実践、の変換に時間がかかり、本番では間に合いません。CPUの性能が悪いPCみたいです。

それでは本番は何も考えられないのか、というと、少しだけ考える余地もあります。

などです。

演奏中には抽象的なものの方が理解しやすい

アドリブをしている間、スケールや運指など細かいことには気が回りません。もしくは、ミストーンを引きずって、次のフレーズが詰まってしまっては本末転倒です。 しかし、前述のような抽象的なものは、意外にも演奏中も受け取ることができます。言語に変換せずに理解できるからです。

例えば、このコーラスでソロを終えようと決めた時には少しアクセルをかけて速いフレーズを演奏したり、緊張感を出すためにわざと長めに休符をとったり。

しかし、周りの音を聞きながら、演奏しつつ、フレーズを生み出しつつ、だと頭がいっぱいになることもあり、ストーリーどころか支離滅裂なソロになってしまうことがありました。 そこで、Yes or Noを題材に、ソロのストーリーを図に書いてみました。

f:id:rakushoo:20200712181954j:plain

汚い手書きで申し訳ありません。。

今日コンボ練習があったのですが、この指針があるだけでどんなフレーズにしたら良いか、迷わずにすみました。小節数と全く対応していませんが、最初のコーラスでは全音符〜四分音符中心、後半にいくほどに速いフレーズで埋める、のような構成を事前に設計しておくと、演奏中にそのストーリーの肉付けをするだけで良くなります。

こんな要素も練習ツールに取り入れられたらな、とぼやっと考えた週末でした。

memcmp,strcmpの戻り値の絶対値にハマった話

「標準関数を再実装する」という課題でstrcmpにハマった話です。

引数の入力のしかたによって、戻り値が異なる

スクールの他の生徒から質問を受けて???となった動作がありました。

int main()
{
    char s1[] = "5";
    char s2[] = "1";
    int a = strcmp("5", "1");
    int b = strcmp(s1, s2);

    printf("strcmp: %s, %s -> a:%d b:%d\n", s1, s2, a, b);
    return 0;
}

<出力結果>
strcmp: 5, 1 -> a:1 b:4

テストケースを作るのに、直接、値を代入した時と別変数に取った時で結果が違う、とのことでした。確かにこれは不思議。
"5" と "1" を比較して期待する結果は 4 かと思います。
となると、引数に直接値を指定すると得られる 1 は何者?となりました。

memcmpに置き換えても同じ動作だったのでひとまずstrcmpについて調べます。

そもそも仕様で戻り値の絶対値は言及されていない

いろいろ試しましたが、

  • 引数をポインタで渡すと、戻り値の絶対値は比較文字の差分と一致する
  • 引数に値 "5" を直接渡した場合は、戻り値が1/0/-1となる
  • const char s1[] = "5"; と定義した場合も同様の結果。

つまり、const無しの入力では値の差分が戻り値に反映されますが、constに対しては、値の大小比較はできるが差分は算出できない、ように見えました。

改めてstrcmpの仕様を見ると、

The strcmp() and strncmp() functions return an integer greater than, equal to, or less than 0, according as the string s1 is greater than, equal to, or less than the string s2.

戻り値は、正の数・負の数、と記述されており、その絶対値についての言及がされていませんでした。推測するに、constの入力は値の比較はできるが差分は算出していないようです。

これからは、memcmp/strcmp の戻り値は正負だけを利用するようにします。

テスト駆動を初めて実施してみた

テスト駆動とは、先にテストケースを用意してから実装を開始する方法です。

コードはいつか必ずテストするので理想的ではあるのですが、期限が迫っていたりここまでに入力値は保証されている場合もあるので、なかなか実践できませんでした。

通っているスクールで規模の小さい実装があり、試してみることにしました。

標準関数の再実装

strXXXやmemXXXなど、標準関数を再実装する課題があり、テスト駆動で始めました。 段取りは以下の手順で進めました。

  1. 仕様を理解する
  2. テストケース・テスト関数を書く
    • 想定内の値
    • 入力範囲の限界値付近
    • ポインタはNULLの場合も
    • 文字数字、可読でない値(\nとか)、ascii外の値(0x80〜)
    • 考えつく組み合わせ
  3. 空関数を用意してコンパイル、ビルドが通る状態にする。
  4. テストを実施する。(ここで全部NGになって良い。)
  5. 実装しながらテスト

2回以上繰り返す処理はマクロor関数化する

例えば、atoiのように、入力値に応じて値を返す関数は他にもあります。(isalpha, isdigitとか)
2回以上繰り返す処理はマクロ化か関数化を考えます。

マクロ化の例
<実装>
#define JUDGE(name, val)   \
   printf("[%s test]\t", # name); \
   if (name(val) != ft_ ## name(val)) {\
       printf("NG - %s: %d,%d\n", # name, name(val), ft_ ## name(val));\
   }\
   else {\
       printf("OK - %s\n", # name);\
   }

int main(void) {
    JUDGE(atoi, "   123");
    JUDGE(atoi, "-123");
    JUDGE(atoi, "1234567890");
    return (0);
}


<出力結果>
[atoi test]     OK - atoi
[atoi test]     OK - atoi
[atoi test]     OK - atoi

マクロは、値の置き換えに限らず、# name は、"名前" としてダブルコーテーションをつけた値として扱われます。
そして、ft_ ## name(val) は、連結した ft_atoi(val) として動作させることができます。

wisdom.sakura.ne.jp

テスト駆動の良かったこと

関数の実装量はかなり増えるので面倒なのですが、良い点もありました。

  • リファクタリングなどちょっとした修正でも確認ができるので安心感がある
  • テストした経過を残せる
  • 仕様の理解が深まった状態で実装に入れる
    (テストを書くために仕様を理解していないといけないので)

これが本質的なテスト駆動のメリットですが、

  • 全てNGのテストが徐々にOKになっていくのが気持ち良い
  • 結果表示のやり方にこだわると綺麗に見せられる

のように、モチベーションに関わる利点もありました。

業務ではどうしても大きな成果を期待されがちですが、こうした一つ一つの関数が堅牢であること、手直しをしても安全を保証できることはかなり大事だなと体感することができました。
テストすごい。

練習ツールを作ろうとしたきっかけ

これまでの記事で紹介したアドリブ練習ツールについて、作り始めたきっかけを振り返って書いてみます。

アドリブソロに惹かれる理由

まず、アドリブソロのどこに魅力を感じているのか?

私はジャズの入り口がビッグバンドだったので、はじめのうちは譜面をいかにジャズのアーティキュレーションで演奏するかに終始していました。 サックスはソロも多いので、そのうちにアドリブもできたら、くらいのつもりでした。

学生の頃は「この人になりたい」と思うプレイヤーの演奏をひたすら真似ていたのですが、最近になって、ソロも自己表現の一つだな、と考えるようになりました。

目立つからカッコいいとか、楽器のテクニックがすごい、とかそういう形になるものはわかりやすいんですが、今はあまり興味はなくて。言葉にするなら、楽器を通じて自分が作りたい世界を表現するためにソロをやる、ってことなのかな。

聞いてホッとするソロ、興奮を呼び起こすソロ、悲しみが癒されるソロ。もしかしたら、恐怖や苛立ちを感じさせるソロもあるかもしれない。言語ではないので聞き手がどういう想いになるかはわからないですが、感情を揺さぶれたら、という気持ちがあります。

どんな世界であれ、まずは表現できるようになるために、アドリブを練習したいんだと思ってます。

まず何から取り組めば良いのか?

話題を元に戻して、アドリブソロをするには何が必要か?

頭の中の流れをみると、

  1. コードを認識
  2. 演奏可能なスケールを浮かべる
  3. コードをつなげるフレーズを浮かべる
  4. 音を出す
  5. ソロ全体を通して緩急・抑揚をつける
  6. リズムセクションとの対話

とかでしょうか。

1.~4.まで無意識でできるようになれば、そこから5.6.の表現に集中できるようになるかな、と思います。

AIがアドリブ演奏をする技術も進んできているようですが、コンピュータ技術が人間の演奏を超える演奏を再現超えないという点よりも、人が表現するのを手助けしてくれる方に私は興味が湧きました。
プログラムの力で1.〜4.を鍛えるような、そんなツールがあればと思って作り始めました。

アドリブ演奏の難しさ、プログラムでどう補うかなど、次回書く予定です。

つづく

herokuサーバー上で、music21のpngファイル出力に失敗する

前回の記事でherokuサーバー上でmusic21を動かす際に、LilypondとmuseScoreのパス設定が必要、まだpng画像が生成されないことを書きました。
music21をherokuサーバーで動かす時に困ったこと - rakushoo blog

結論としては、Lilypond(2.20.0-1)内のgsバージョン不整合が原因でした。Buildpackでgsもインストールするようにし、Lilypondから参照するバイナリと差し替えることで対応したところ、png画像が出力されるようになりました。

解決までの経過

ローカルでは動くしパスも通ってるのに何故か画像が出力しない。 png変換でwarningが出ていました。

Converting to PNG...  
warning: `(gs -q -dSAFER -dEPSCrop -dGraphicsAlphaBits=4 -dTextAlphaBits=4 -dNOPAUSE -dBATCH -sDEVICE=png16m -dAutoRotatePages=/None -dPrinted=false -sOutputFile=Oleo.png -r101 -fOleo.eps)' failed (256)  
fatal error: failed files: "/app/media/output/Oleo"

とりあえず、gsなんちゃらのコマンドで失敗していると。ここで3日ほどハマったので、検討した手順だけ残しておきます。

  • そもそも入力ファイルがどういう手順でpngに変換されるかを確認。
  • ローカルでは、lyファイル、epsファイル(複数)、pngファイルが作成されている。この時点で拡張子なしのファイル(.lyファイル)のみ作成されていた。→ epsファイルの変換で失敗している、ぽい
  • music21のソースをfolkして確認。コード見ても異常なし。
  • herokuサーバーにログインして、lilypondを直接実行したところ、.epsファイルが一つ作成されるがやはりgsで失敗。

lilypondの不具合だということまではわかった

不具合は、music21ではなく、Lilypondが受け持つ処理で、gsコマンドが失敗している、とまでわかった。外部アプリの不具合は辛い。。まあ仕事じゃないし、諦めてローカルで画像を作るようにPCアプリにしようかとぼんやり考えながら休憩。

ふと、デバッグログとか無いかな?、とlilypondのヘルプを見たら大当たりでした。
ログレベルを設定して実行したところ、gsコマンドのバージョン不一致が原因とのこと。

/app/lilypond/usr/bin/lilypond -f png -dbackend=eps --loglevel=DEBUG -o /app/media/output/test /app/media/output/sample.ly
〜省略〜
[test.eps] Invokinggs -dSAFER -dEPSCrop -dGraphicsAlphaBits=4 -dTextAlphaBits=4 -dNOPAUSE -dBATCH -sDEVICE=png16m -dAutoRotatePages=/None -dPrinted=false -sOutputFile=test.png -r101 -ftest.eps'...
gs: Interpreter revision (926) does not match gs_init.ps revision (921).

lilypond内のgsバージョン不整合が原因

インストールしたLilypond(2.20.0-1)から通るパスのgsバージョンは9.26だが、途中で必要となるgs_init.psバージョンが9.21と不整合となっていた。

Buildpackでgs(Ghostscript)9.21をインストールし、lilypondから使用するgsと差し替えることにし、無事に画像出力ができるようになった。

GitHub - rakushoo/heroku-buildpack-ghostscript: Ghostscript for Heroku Cedar

自分のアプリから呼び出したアプリ内の不具合調査は疲れるけど、良い経験になりました。

music21をherokuサーバーで動かす時に困ったこと

自作アプリをWebサービスとして公開するにあたり、herokuを使うこととしました。

理由は、

  • Python-Djangoの情報が豊富そう
  • 無料プランがある
  • デプロイがしやすい

あたりです。とにかくWebサービスのアップが初めてなのでハードルが低くて経験できるものを選びました。

デプロイまでは他サイトを参考にこぎつけたのですが、music21が動作するまでに苦労した点を残しておきます。

png出力ができない

画像出力には、下記の処理を実施していました。

from music21 import stream  
〜省略〜  
s = stream.Score()  
s.write('lily.png')

ローカルではうまく出力できたのですが、本番環境では画像が生成されない問題にぶつかりました。
どうも、lilypondの呼び出しで失敗しているようでした。確かにインストールした覚えがない(笑)。

importしない外部アプリをどうやってインストールするか?

herokuのデプロイ時、pip installできるものはrequirements.txtを用意することで自動的にインストールされます。

しかし、music21を動かすために必要な、lilypond、museScoreは別途インストールが必要になります。(どちらも画像やXmlなど出力に関するもの)

herokuでは、BuildPakcsという仕組みを利用することでデプロイ時に必要な外部アプリをインストールが可能になります。 他の開発者のリポジトリもありましたが、バージョンが古いことや、配置するパスを指定したかったので、folkして自前リポジトリを用意しました。

https://github.com/rakushoo/heroku-buildpack-LilyPond

https://github.com/rakushoo/heroku-buildpack-MuseScore

そして、実装側のmusic21に対して下記の設定を追加しました。

from music21 import environment  
us = environment.UserSettings()  
us['lilypondPath'] = '/app/lilypond/usr/bin/lilypond'  
us['lilypondVersion'] = "2.20.0"  
us['musicxmlPath'] = '/app/tool/MuseScore-3.4.2-x86_64.AppImage'  
us['musescoreDirectPNGPath'] = '/app/tool/MuseScore-3.4.2-x86_64.AppImage'   
※ /app/はherokuサーバー上のソースコードが展開される場所

ひとまずBuildPacksに追加してデプロイをしたところ、write('musicxml')でのMusicXML出力はできるようになりました。

lilypondを使ったpng出力がまだできない状態だったので、別記事で対応方法を書きます。

自分のPCで作ったアプリを公開するには?

前回までで、意図した順に音符を表示させることはできるようになりました。

表示させる内容についてはまだまだ詰めるとして、他の人にも使ってもらうにはどうしたら良いか?

今のままだと、PCにPythonをインストールしたり、githubからソースコードを取ってこないといけないのは厳しい。

もっと簡単に使ってもらえる方法を検討しました。

作ったプログラムを公開するには?

馴染みがあるのは、スマホ(もしくはPC)アプリかWebページ上で動くもの。iOSでのアプリ開発は少しだけかじったのですが、iOS上でpythonを動かせるのか?そもそも譜面表示できるのか?と不安になり、Webサービスとして提供する方針にしました。

PythonにはDjangoというフレームワークがあり、WebサービスPythonを動かすのに必要なGUI画面と内部データのやり取りを担ってくれるとのこと。組み込みやってたのでこんな便利なものがあるのは助かります。

という事で、Python x Django を使ってWebサーバーで動くソフトを作る という方針に決めました。

Djangoフレームワークの情報

どうもチュートリアルが充実しているようで、ひとまず下記ページを参考に、サンプルプログラムを写経しながら実装してみました。
はじめての Django アプリ作成、その 1 | Django ドキュメント | Django

自分のPC環境でサーバーを立ち上げて、自分のアプリと連携させて動くようにがひとまずの目標。ここまではどうにか動くようになりました。

f:id:rakushoo:20200611181631p:plain

色気も何もない入力ページを用意して、この入力画面から

f:id:rakushoo:20200611182637p:plain

中で作った譜面が表示されるようになりました。

f:id:rakushoo:20200611182510p:plain

ここまでは自分のPC内でのサーバー動作の話。 次回は外のサーバーにアップロードしてみます。