ymemo

ハードルをすごく低くして書いていこう

リズムゲームを作るのに必要っぽいこと(準備編)

この記事はDark - Developers at Real Kommunity Advent Calendar 2015の22日目です。

寝るまではまだ22日目なんだ…!

って書いてからその認識さえ一日遅れていることに気づきました。本当です

このカレンダーも終わりが近づいて来ましたね…!


みなさん、音ゲーやってますか? 僕は某フェス某ステージのイベントがたまに被ったりして大忙しです。

こういったリズムゲームをやっていると、たまに以下のような不満を感じることがあります。

  • 好きな曲なのにリズムパターンやノートの置き方が気に食わなくていまいち盛り上がれない
  • もう少し難しくてもいいのにと思う
  • 逆に難しすぎだろアホと思う

ざっくり言ってしまえば、「なんかリズムが気に入らないな」という話。

リズムゲームのパターンの作り方はゲームによっても曲によっても様々で、分かりやすい所ではパターンがドラムスに寄っているか旋律に寄っているか、そのゲーム特有の特殊な打ち方(押し続けやフリックなど)をどの程度用いるかというような違いがあります。

このパターンが自分の好みに合わない場合、とくに好きな曲をプレイしていると、とても残念な気持ちになってしまうことがあります。


自分で作る

もしそんな不満を抱えているあなたがエンジニアなら、気に入らないものは自分で作ってしまいましょう!

この記事では、リズムゲームを作るのに必要そうな技術的課題以外の、準備の部分を中心に取り組んでみました。

もちろん僕はリズムゲームを作るプロではないので、本場の方法と大きく異なる部分があるかもしれませんんが、一応そろえれば作れるよ、というものとして書いています。

この記事を見てもおもしろくなさそうな方:
  • 今あるリズムゲームに特に不満がないか、リズムゲームに興味のない方
  • 既にリズムゲームを自分で作れる、仕事で作っているという方 (→ 生暖かい目で見守ってください)
  • DTMをやっているエンジニア (→ たぶんすべて知っている内容です)
  • リズムゲームの演出とか運用とかについて知りたい方 (→ 僕も知らないっす)

こんな手順になります。

音源にあわせてリズムパターンをつくり、最終的に音源の再生時間基準でどこをタップするか、みたいな情報を含んだデータに落としこむのが基本的な流れ。

  1. 音源を用意する*
  2. DAW的なものを用意する
  3. 曲のテンポを計測する*
  4. 音源の位置合わせをする*
  5. リズムパターンを打ち込む
  6. MIDIファイルと音源(*)を吐き出す
  7. MIDIファイルの中身を変換する

何かアプリケーションを作るところまでできれば良かったんですが、打ち込みに3時間くらいかかって力尽きました……

ちなみにこのうち、*の付いている手順は、少なくともサービスとしてまっとうな方法でリズムゲームを作る場合必要のない手順です。

1. 音源を用意する

何はなくとも、曲の音源です。

今あるリズムゲームに不満がある方は、実際のリズムゲームから吸い出すのもよいかもしれません。 某ステージには自分のプレイ音が混じらないMVというモードがあるので余裕ですね! (こうして吸いだした音源を他人に頒布したり自分で楽しむ以外の諸々の用途に供するのはいけないことなのでやめましょう!!あと曲を聞きたいなら買おう)

自分で用意する場合も、1曲のプレイ時間として適当な(あと打ち込みで死なない)長さに気を付けます。

2. DAW的なものを用意する

DAW = Digital Audio Workstationです。

f:id:drowse314:20151224040846p:plain

実際のところ、MIDIと音声ファイル等を同時に読込み・編集・書出しできるものならなんでもOKですが、こういうのはDAWで検索するのが一番でてきやすいです。

ちなみにMIDIは今のところ、いつどんな音が鳴るかを書き込める電子的な楽譜みたいなものと思っておけばとりあえずよいでしょう。

今はフリーでもちゃんと使えるものがたくさん出ていて最高ですね…昔の無料シーケンサーなんて...うっ‥頭が…

今回はTracktionを使いました。少し昔のバージョン(Tracktion 4)が無償で提供されています。

3. 曲のテンポを計測する

今どきの音楽でDAW的なものを介さずに制作されているものは恐らくないと思われ、その場合、打ち込みの部分と生演奏・録音の同期をとるために、必ず曲全体が精確なテンポに従ったタイムラインの上で管理されているはずです。

しかしながら、今回のようにひとの曲を勝手に使ってどうこうしようという場合、自力でテンポを計測して知る必要があります。

f:id:drowse314:20151224040503p:plain:right:w200

今回使ったのはこちら。非常に典型的なBPM(= beats per minute)測定アプリで、テンポに従ってタップすることでタップ間隔の平均的なテンポを知ることができます。

曲を聞きながら、ひたすら無心でタップしましょう。

リズムゲームで培った精確なタップ力が試されますね。

4. 音源の位置合わせをする

DAWを起動して新しいプロジェクトを作り、計測したテンポを設定したら、用意した曲の音源を適当なトラックにロードします。

f:id:drowse314:20151224041816p:plain:left:w250

音源の内容がトラックにべーっと貼り付けられたら、音源の最初の表拍などをDAWのタイムラインの拍と合うように、音声トラック上を移動します。

プロジェクト全体を再生しながら、録音用のシグナル音みたいなものがあればその音と、なければ適当な音をMIDIトラックに打ち込んで、DAWの拍と音楽の拍が合うようにします。

当然ですが、テンポが合っていない場合は曲と他の音がだんだんズレてきます。 そうでない場合は初めから終わりまで同じ時間のズレが維持されてかなり判定が難しいですが、頑張って心の耳で合わせます。

5. リズムパターンを打ち込む

いよいよリズムパターンの打ち込みです。

自分の魂のビートを打ち込むことで、リズムゲームでしっくり来なかった鬱憤を存分に晴らします。

ただし、この作業は恐ろしく時間がかかります…

f:id:drowse314:20151224042904p:plain

ピアノロール(画像)というのはおおよそ人間の使用に耐えるものではなく、プロの人は電子楽器等で演奏したものを自動補正することで打ち込むため、今後もまともな進化は期待できない[要出典]UIです。

気に食わないリズムパターンを叩くよりここでよっぽどフラストレーションがたまっている気がしますが、まあ気にしないことにします。

タイミングのデータを作っているだけなので楽器を選ぶ意味はありませんが、ここでトラックや音の高さなど打ち分けておくと、後 の処理で参照して何かの目印に使うことができます。

6. MIDIファイルと音源を吐き出す

打ち込みが終わったら、打ち込んだリズムパターンの部分をMIDIファイルにして出力します。

また別に、打ち込んだパターンをミュートにして音源の部分も出力します。これによって、位置合わせした分の空白が反映された新しい音源を作ります。

7. MIDIファイルの中身を変換する

ここに来てやっとエンジニアらしい作業です。

MIDIファイルから情報を抜き出し、曲の音声ファイルの特定の再生時間でどこをタップしてください、みたいなデータを作ります。

MIDIファイルを扱うためのライブラリはきっと言語ごとに一つくらいはあると思うので、そういうのを使います。今回はPython製の以下を使いました。

github.com

こんな感じでダイレクトにファイルを読むインターフェイスを用意してくれているので、すぐに本来の作業に取りかかれますね。

import iomidi

song = iomidi.read('midifile.mid')

MIDIの構造

MIDIに変換をかますには、MIDIファイルの構造を知る必要があるでしょう。

iomidiのオブジェクトのプロパティを参考にざっくり見てみると、以下のようになっています(もちろん実際はJSONではありません)。

// song
{
    header: {
        trackCount: 5,
        division: 960,
        frmt: 1
    },
    tracks: [
        trackObject,
        trackobject,
        ...
    ]
}

ファイル全体の情報が書かれたヘッダに、独立したトラックがいくつか含まれていることがわかります。

ヘッダで重要なのはdivisionです。ファイルの中の時間は全てtickと呼ばれる単位の整数倍で表されていますが、このtickの分解能を表すのがこの値で、tickが1秒の何分の一かを示しています。

ここでは960なので、おおよそ1tickは1msくらいになります。

トラックは先に打ち込みで作った数+1だけ存在するはず(トラック0はなんかシステム的なやつが入っています)。トラックの中身を見てみます。

// song.tracks[1]
{
    events: [
        eventObject,
        eventObject,
        ...
    ]
}

うん、もっと中を見ろってことですね。

eventの中身はこんな感じです。

// song.tracks[0][N]
{
  NoteOnEvent: {
    velocity: 96, 
    channel: 0, 
    key: 50, 
    delta: 1650
  }
}

案外シンプル(?)ですね。

理解しやすいものの定義はこんな感じです。

key: 打ち込みで入れた音の高さ。ドラムとかにもちゃんとついている。

ごく普通の音を鳴らすイベントは、基本的に音を鳴らし始めるNoteOnEventと鳴らし終えるNoteOffEventの合わせ技でできていて、同じkeyNoteOffEventが発生すると前回のNoteOnEventの音が止まる感じになります。

velocity: 速さ…ではなく音の強さを言います。今回の処理には関係なし。

channel: MIDIのしくみ系の値。今回の処理には関係なし。


deltaについて

MIDIはそもそも、電子楽器などの演奏データを楽器や機器の間でリアルタイムに交換するための信号の規格で、MIDIファイルの中身も少し変わった形で記述されています。 deltaはその事情を反映した値になっている気がします。

deltaは、そのトラックの前回のイベントから自分までの時間差をtickで表した値です。

今回欲しいのは音源のどの再生時刻にタップが入るかなので、トラックの最初のdeltaから順次足し合わせる必要があります。

なんかこんな雰囲気ですね。

def to_tap_sequence(midi_track):
    accumulated_time_ticks = 0

    for event in midi_track.events:
        accumulated_time_ticks += event.delta

        yield dict(
            tap_position=select_position(event),
            time_ticks=accumulated_time_ticks)

このdeltaさえなんとかすれば、いい感じのデータが作れそうな感じがします。

必要に応じて、NoteOnEventだけを選出する必要はあるかもしれません。

あとは時間単位をミリ秒等になおして、

import decimal

def tap_sequence_with_ms(seq, midi_header):
    for event in seq:
        event['time_ms'] = ticks_to_ms(
            event['time_ticks'], midi_header.division)
        yield event

def ticks_to_ms(ticks, time_division):
    return float(
        # a tick as milliseconds
        1 / decimal.Decimal(time_division) * 1000
        # multiplied by #ticks
        * ticks)

必要に応じてトラックのイベントをマージすれば、

def merge_tap_sequences(seq_a, seq_b):
    # 効率悪..
    rev_seq_a = list(reversed(list(seq_a)))
    rev_seq_b = list(reversed(list(seq_b)))

    while rev_seq_a and rev_seq_b:
        if rev_seq_a[-1]['time_ticks'] <= rev_seq_b[-1]['time_ticks']:
            yield rev_seq_a.pop()
        else:
            yield rev_seq_b.pop()

    while rev_seq_a:
        yield rev_seq_a.pop()
    while rev_seq_b:
        yield rev_seq_b.pop()

なんかできた気がする!

こちらがここまでで作ったものになります。

github.com

一応CLIが付いていて、なんか結果を吐いて音源と比較してみるとどうもtime_msの時間がおかしい気がするんですが、まあまだこの先を作っていないので仕方なし…とりあえずデータの作り方のコンセプトは合っているはずです。

あとは、作りたいゲームの特性に応じて情報を作ってイベントにくっつけくっつけしていけば、データは完成するのではないでしょうか(適当)。


さあ、ゲームらしきものを作る段には全然入ってませんが、ここまでのデータがそろっていれば、エンジニアならあとは何となく作れる気がして来たのではないでしょうか(自分も年末にやり切りたいです…いつかゲーム実装編が書かれる…かどうかはわからない)。

音楽ゲーム系のアプリケーションは何となく難しそうなイメージがありますが、本当に難しいのは気持ちよくプレイするためのタイムラグとか処理落ちをなくすとかの部分で、DAWをさわれてMIDIの知識をつければ、案外その前までは作れてしまうものです。

これを読んで頂けたエンジニアの人は、興味があったら是非作ってみて下さい!

そしてできたら僕にも遊ばせてください!


次は@sinamon129です!

あれ…もう書かれているぞ…?!おかしいな…(おかしくない)

【ポエムと】闇に飲まれるな【宣伝】

この記事はDark - Developers at Real Kommunity Advent Calendar 2015の15日目です。

Classic ASPの残念な話をネタで書こうと思っていたんですが、書いていたらあまりにつらくなってしまって無理だったので替わりのポエムです。

特に酔って書いたわけではないので、逆にちゃんとポエムできていないかもしれませんが、ご容赦下さい。(文章が長くてわかりにくいのはデフォルトです)

このコミュニティのターゲット、「(だいたい)新卒」な皆様の中には、きっとそろそろ自分がエンジニアと言う職種の暗闇の部分に片足を踏み入れているように感じている方がいるのではないかと思います。

そんな人に向けてのお話です。

読んでも得しなそうな方

  • 仕事が楽しい方 (→ 意味もなく暗い気持ちになるかもしれません)
  • エンジニアとしての自分の成長について常に危機感をもって行動している方 (→ 気分を害するかも知れません)
  • 死にたいほどつらい方 (→ 病院へ)

ソフトウェアエンジニアは、生きながらにして死んでしまうことがあるように思います。

f:id:drowse314:20151216012649j:plain:w300:right

本当に命を削って仕事をされている方には申し訳のないことですが、ここで言っているのは、

それなりの健康を維持できる環境で働いている中でも以下の様な状態に陥って、エンジニアとしてゆっくりと死んでいくという話です。

  • 開発することが楽しくなくなる
  • 仕事上の問題解決はソツなくできるので今のままで別にいいような気がする
  • 組織運用上の問題は無視するか、技術で力ずくの解決をしようとする

誰しも疲労が蓄積すると好きなことも疎かになってしまうものですが、ゆっくり死んでいくタイプのエンジニアはたまにはちゃんとゆっくりできるのが普通なので、問題はむしろ別のところにあるように思います。

より良い仕事の成果を自然に目指していけるという希望を失うこと

多くのエンジニアは、開発の仕事さえ与えておけばずっと生きている虫のような生きものとは違うので、エンジニアを目指した時の気持ちを再確認する機会が目減りしてしまったとき、誇りを失い、自分がエンジニアとしてできることが何なのか忘れてしまうものです。

  • 古代遺産を大人の事情で守らなければならない
  • 政治的な理由で選択できる技術が限られる
  • 実現する価値が会社のビジョンと関係ない

説明はしませんが、きっとここまで読み進められた人には分かってもらえるでしょう。

それまで自分のプライドをかけて選んできた技術、方法、名前、そして何に貢献するのか ということ。

こういった気持ちを奪われたエンジニアは、日を追う毎に受け身がちになり、開発する機械のような振舞いで身を守るようになります。


外に目を向ける

ずっと前から何度も言われてきたことですが、会社や仕事の範囲にとどまらず、外に目を向けて客観的に自分の置かれている状況を見つめること。

僕自身、何となく静かに死んでいきそうになっていた頃、@moschanと@arataに運良く誘ってもらうような形でDarkに関わるようになって、イベントを開催して、

この世にはこんな恵まれた環境でエンジニアをやっている人がいるのか、とか、技術を好きな人がこんなに集まってくれるのか、という嬉しい気持ちがあったりした一方で、

自分を取り巻く環境を変えるのは勿論ですが、その後自分の状況とどう向き合っていくべきか、意外とすぐに答えが出るものではありませんでした。


老害について

こういった文脈でよく話題に上がる老害という単語があります。

僕自身はあまり好んで使う言葉ではないですが、一方ソフトウェア開発の世界に限らず、どんな業界・ドメインでもかなり普遍的に見い出される概念のように思います。

この言葉はだいたいいつも様々な負の感情と結びつくのでなかなか捉えづらいものがありますが、共通して彼らに見られるのは、

自分が今まで作り上げ、継続してきたやり方に誇りを持っている

という特徴です。

これは、ゆっくり死んでいく系エンジニアから見るとげに羨ましいことです。

彼らは事実として、上で書いたような僕らの苦しみに関与してはいますが、実はある意味、僕らが渇望する幸せなエンジニアの一つの形を体現しています(いました)。

彼らはちょうど技術者として幸せな時間を過ごせるタイミングをつかんで一時代を築いた結果なのだと思いますが、

重要なのは一時代を築いたことではなく(申し訳ないですが)、

一時代を築くにあたって、誇りを持ってエンジニアリングに対峙し、またその誇りを継承してきた彼らの姿勢にあります。

これはある意味で、エンジニアとしての一つの理想像とも言えないでしょうか。

もし彼らと同じ組織に所属し、同じビジョンを目指しているのならば、彼らが本当はどんな価値観をなぜ大切にしているのかよく知り、どのような視点で会社の目指すものを見つめており、僕らとの間の溝が一体何なのか理解すべきです。

その上で

組織を理解して変えていくためには、どの程度の時間と労力が必要でしょうか。

自分にそれを支払うことはできるのか。他にこの作業を行うべき人は、今何をしているでしょうか。

僕らと彼らの問題がクリアになって解決した時、その先に僕らの目指すものはあるでしょうか。 彼らの視点は尊重するに値するものだったでしょうか。

それらは、本当に僕らの問題として取り組むべきことなのでしょうか。


誘い

@arataが最近言っているとおりDarkは雑なコミュニティですが、雑だからこそ誰にでも入り込める余地があるし(不審者のご入場はお断りしております)、色々な状況や考えの異なる人たちが集まれる場所になっているように思います。

f:id:drowse314:20151216012458j:plain:w300:right

ぜひDarkのイベントに足を運んでみてください。

いろいろな人の話を聞いて、他のひとが目指す価値と自分の目指す価値、他のひとの環境と自分の環境について比較してみてください。

別々の誇りをもった二人がぶつかれば、トシに関わらずきっとそこにROUGAIが生まれます。

今の自分の闇は許容すべきなのか、

あるいは闇を受け容れるとして、状況を打開するためのコストをかけるべきなのか、

自分は将来、いったいどんなROUGAIになりたいのか、

一緒に考えていきましょう。

こんなアドベントカレンダーを書いても(きっと)平気なコミュニティですので、気軽に遊びにきて下さい。


運営のくせに恩恵を受けてばっかりで闇の話しかしないし、なんかちゃんとエンジニアとして活躍していてコミュニティの話題を作ってくれている人たちには申し訳なさが結構あるんですが、

やっぱり今沈んでいるたくさんのエンジニアに、道をとりもどすきっかけをつかんで欲しくて、このコミュニティはそういう場としてあり続けられるんじゃないかと思っているのです。

まとめ

  • 闇に飲まれているエンジニアの皆様、Darkへ参加しませんか?(たのしいよ!)
  • 闇から抜け出した先で得られるものと、闇に立ち向かって得られるものを天秤にかけよう
  • という自戒混じりの宣伝

老害を理解する云々はだいたいこちらの受け売りなので少しでもピンときた人は見てみると良いと思います。

明日はきっと光の話題です!乞うご期待!

机の上にシルクジャスミン

この記事はDark - Developers at Real Kommunity Advent Calendar 2015の11日目です。

こんにちは、Darkでピザ注文職人[要出典]をやっている@ymatです。

闇に飲まれている筆頭として何か書こうかと思ったんですがそれは16日目にして、今日は心癒される話題(?)です。

ちなみに、エンジニアリングのえの字も出てきません。

亜熱帯オフィス

僕が毎日通っている弊社の6Fフロアは夏も冬も驚くほど暑く、室内の温度は常に25度に迫り、木枯らしが吹くような肌寒い日でも半袖で過ごせるのが特徴です。

働いている身としては眠くなるし、冬は外気温との差が大きすぎて服装の調節が難しく、体調を崩しやすくなるため出来ることなら改善してもらいたいものですが、 この特殊な環境は、とある一つの趣味を発生させます。

そう、観葉植物の育成です。

こちらは僕と同じチームの先輩のデスクの様子です(※特別な許可を得て撮影しています)

f:id:drowse314:20151211134654j:plain:w500

このうちいくつかは光触媒の材質で作られた空気清浄作用をもつ人工物とのことですが、 いかがでしょうか、まさにジャングルです。

一年を通して温暖な気候が保たれる上に日付が変わってもなお何時間にもわたって煌々と人工の光を湛える僕らのオフィス[要出典]は、 あたたかい地域でこそ青々とした葉を伸ばす観葉植物たちにとって、うってつけの環境なのです。

机上シルクジャスミン

そんな訳で、僕もはじめてみました。

f:id:drowse314:20151211135902j:plain:w500

シルクジャスミンという種で、和名をゲッキツ(月橘)といい、名前が醸し出す通りミカン科の植物です。

詳しいバイオロジーが知りたい場合はWikiでも見て頂くとして。

f:id:drowse314:20151211140749j:plain:w500:right

この美しさ。

一年中つやのある鮮やかな緑の葉を輝かせ、幹はなめらかで白けたオーガニックな雰囲気を纏うその姿はまるで生命力の象徴のようでもあり、 また神聖な儀式祭具のようですらあります。


そもそも、名前からして神々しさがあります。

今、俺の机の上にはシルクジャスミンがあるんだ

というだけで、ふだん、開発する機械[要出典]のように働いているわたしたちに、何か忘れかけていた誇りのようなものを思い出させてくれる気がしませんか。

かんなぎ」という作品を読んだことがあるでしょうか。 3巻に、

あの時の きれいなひとが 家にいる
 ―― それだけで いつも うれしかった

(※若干の整形を施してあります)

という一節がありますが、そんな感じです。

苗木ケア

このシルクジャスミンですが、机の上に置く観葉植物としてはかなり変わり種のようです(そもそもミカンですし)。 というのも、いくつか普通の観葉植物にはあまり見られない特徴があります。

かなり水をあげて平気 (乾燥に弱い)

大抵の観葉植物は水やりの過多に弱く、あげる周期は土の表面が乾いてから3,4日後、というのがセオリーです。

しかしこのシルクジャスミン、土の表面が白く水分が飛んだ状態になると途端に目に見えて顔色が悪くなるため、土が乾ききったタイミングで間を空けずに水やりを行うくらいの気概が必要になります。

つやつやの葉も乾燥に弱く、特に空調の風が直接当たるようなロケーションで育成する場合は、定期的に霧吹き等で葉の表面を保湿しておく必要があります。

↓水を滴らせると美しいの図(わかりにくい)

f:id:drowse314:20151211141605j:plain:w300

日照が大事

基本的に日当たりの良い場所でないと花や果実を付けるのは難しいようで、室内はなかなかにハードな条件だったりします。

あと日照って書いた時の日光東照宮感は異常。

うどんこ病に弱い

なんだそれはという話ですが、様々な植物に発生するカビの一種が原因の病気で、 罹患すると白いうどんの粉のようなものが葉に付着し、草木の健康を奪います。

シルクジャスミンは葉が乾燥するとうどんこ病になりやすいようで、やはりこの対策も水分補給が鍵 となります。

カイガラムシがすぐつく

僕の2年前の入社時に元気に葉を広げていた弊社6Fの2本の観葉植物は、今年の春ごろに揃って産業廃棄物として処分される憂き目に遭いました。 その原因となったのがカイガラムシです。

あれらはどこからともなく現れて植物の枝葉に付着し、またたく間に増え、 まるで植木がファーを纏っているかのように毛で真っ白にしてしまうおっかないやつらです(もちろんその後枯死します)。

シルクジャスミンは産地によって出荷禁止になるほどカイガラムシの獲得率が高い[要出典]とのことで、 発生実績のある弊社6Fオフィス(本当どっから入ってきたんだ)では特に注意して除去していきたいところです。

うどんこ病とあわせて、とにかく白いヤツには気をつけろということですね。 /人◕‿‿◕人\

常緑ヒーリング

f:id:drowse314:20151211141126j:plain:w300:right

シルクジャスミンは特に芽吹く力が強く、うちの木も不利な室内で育て始めてまだ1ヶ月足らずですが、 既にみずみずしい若葉を順調に伸ばしつつあります。

いつでも目線をあげれば健気に育つシルクジャスミンのある生活は、想像以上に癒しの力を秘めています。

皆さまも机の上に生命の輝きを導入してみてはいかがでしょうか。

まとめ

  • 暑い
  • シルクジャスミンに癒されるの良い
  • 写真ヘタだしiPad Miniだけだと限界ある(エフェクトでごまかしてすみません)

f:id:drowse314:20151211141320j:plain:w500

あすはDarkのメインイベント、(だいたい)新卒エンジニア向け技術交流会 vol.5です!