LoginSignup
7

More than 5 years have passed since last update.

Flaskを用いたWebアプリケーションで音を再生する

Posted at

はじめに

以前作ったコード進行を自動生成するサービスで、生成したコードを音を鳴らして確認できるようにしたい。
https://chordprogressor.herokuapp.com/
https://qiita.com/Giita2000/items/b0a500762a127b7ad69b

できた成果はここ。
http://chord-progressor.xyz
Herokuではなぜかどうしてもうまく動作しなかったので、サーバーを立ててみたがサーバー難しい、、、
なんでかプログラムがしょっちゅう落ちてるし、自動起動もうまく動かない、、、

MIDI.JSを用いて音を鳴らす

サイト上で音を鳴らすには波形をそのまま出力する方法もあるが、容量が大きくなることを考えるとMIDIを使った方が良さそう。MIDIファイルをならすにはMIDI.JSというJavascriptのライブラリが簡単に扱えるみたい。
https://qiita.com/Suna/items/68fa9ae40721cd7c55da

MIDI.JSでは最初にライブラリを読み込めば,midファイルを指定して再生することができる。
この場合アプリケーション側でmidファイルを作成することになるが、ファイルをどう扱うべきか。
Webアプリケーションでは複数のアクセスが同時に来る場合があるので、単一の名前で保存してしまうと、ある人が音を鳴らそうとしても同じタイミングでアクセスした違う人のために生成されたファイルが再生されてしまう。

こういう場合普通はどうするのかよく分からなかったが、ランダムに割り当てたIDでファイルを扱うことでうまく動作させることができた。

入力したコード進行をならすサンプルプログラム

MIDI.JSを用いて音を鳴らせるWebアプリケーションのひな形プログラムを作成した。内容としては入力したコード進行を鳴らすだけ。
コード進行のmidファイルの生成方法は以下の記事。
https://qiita.com/Giita2000/items/c0fea6e415bf7d743e16

サーバーのフレームワークとしてFlaskを用い、Pythonプログラムによりmidファイルを作成、MIDI.JSによりファイルを再生する。

構成としては以下のようになる。

.
├── app.py
├── MidiCreate.py
├── tmp.py
├── tmp
│   ├── 1234.mid
│   └── 5678.mid
└── template
    └── index.html

tmpフォルダを作成し、そこにmidファイルを出力する。

まず、最初のindex.htmlには入力フォームがあり、そこにコード進行をスペース区切りで入力する。
Generateを押すとpostされるので、それを受け取ってコード進行のmidファイルを生成、ランダムな数字のIDの名前で出力する。
そのIDのファイルをMIDI.JSで再生するindex.htmlを生成する。

image.png

ここでtmpフォルダを用いたが、flaskで任意のフォルダを使用するにはBlueprintを使う。
https://www.yoheim.net/blog.php?q=20160905

index.html
<!doctype html>
<html>
<head>
<title>{{ playmidi }}</title>
<script type='text/javascript' src='//www.midijs.net/lib/midi.js'></script>
</head>
<body>
{% if fileID %}
<a href="#" onClick="MIDIjs.play('tmp/{{fileID}}.mid');">Play</a>
{% else %}
<form action="/post" method="post">
<input type="text" id="chords" name="chords" placeholder="例 : C G Am">
<button>Generete</button>
</form>
{% endif %}
</body>
</html>
app.py
from flask import Flask, render_template, request
import MidiCreate
# 自身の名称を app という名前でインスタンス化する
app = Flask(__name__)

# Blueprintを読み込む
import tmp
app.register_blueprint(tmp.app)

@app.route('/')
def index():
    # index.html をレンダリングする
    return render_template('index.html')

# /post にアクセスしたときの処理
@app.route('/post', methods=['GET', 'POST'])
def post():
    if request.method == 'POST':
        # フォームから「chords」を取得
        input_chords = request.form['chords']
        if input_chords != '':
            fileID = MidiCreate.midi_create(input_chords)
            return render_template('index.html',fileID=fileID)
        else:
            return render_template('index.html')

if __name__ == '__main__':
    app.run() 
tmp.py
from flask import Blueprint
app = Blueprint("tmp", __name__,
    static_url_path='/tmp', static_folder='./tmp'
)
MidiCreate.py
import numpy as np
import pretty_midi
import re

def midi_create(imput_chords):
    root        = {'C':0,
                   'C#':1,
                   'D♭':1,
                   'D':2,
                   'D#':3,
                   'E♭':3,
                   'E':4,
                   'F':5,
                   'F#':6,
                   'G♭':6,
                   'G':7,
                   'G#':8,
                   'A♭':8,
                   'A':9,
                   'A#':10,
                   'B♭':10,
                   'B':11,}

    chord_type = {'':np.array([0, 4, 7]),
                  'm':np.array([0, 3, 7]),
                  '7':np.array([0, 4, 7, 10]),
                  'm7':np.array([0, 3, 7, 10]),
                  'mM7':np.array([0, 3, 7, 11]),
                  'M7':np.array([0, 4, 7, 11]),
                  'dim':np.array([0, 3, 6, 9]),
                  'aug':np.array([0, 4, 8]),
                  'add9':np.array([0, 4, 7, 14]),
                  'sus4':np.array([0, 5, 7]),
                  '7sus4':np.array([0, 5, 7, 10]),
                  'm6':np.array([0, 3, 7, 9]),
                  '6':np.array([0, 4, 7, 9]),
                  'm7-5':np.array([0, 3, 6, 10]),
                  'm6':np.array([0, 3, 7, 9]),
                  '9':np.array([0, 4, 7, 10, 13]),

    }


    def split_chord(chord):
        j=chord    
        c=j
        if len(c)>1:
            c=c[0:2]
            if c[1]=='#' or c[1]=='♭':
                c=c[0:2]
                j=j[2:]

            else:
                c=c[0:1]
                j=j[1:]
        else:
            j=''
        return c, j

    pm = pretty_midi.PrettyMIDI(resolution=960, initial_tempo=120)     
    instrument = pretty_midi.Instrument(0) 

    chords = np.array(re.split(" +", imput_chords.rstrip()))
    d_time = 1#コードを鳴らす間隔
    time = 0
    for chord in chords:

        croot, ctype = split_chord(chord)
        notes = 60 + root[croot]
        if ctype in chord_type:
            notes += chord_type[ctype]
        else:
            notes += np.array([0, 12])

        for note_number in notes:
            note = pretty_midi.Note(velocity=100, pitch=note_number, start=time, end=time+d_time) 
            instrument.notes.append(note)

        time = time + d_time

    pm.instruments.append(instrument)

    fileID=str( np.random.randint(1000) )
    filepass='tmp/' + fileID + '.mid'
    pm.write(filepass)

    return fileID


Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7