#まえがき
LilyPond及びそれを用いたライブラリやツールを使うと、
テキストで文字を書くことによって五線譜やMIDIファイルを作ることができます。
今回はLilypond周辺ツールを使ってより効果的に楽譜を作成することにフォーカスします。
- こちらは、音楽ツール・ライブラリ・技術 Advent Calendar 2019 12/19分の記事です。
2行で
- 既存の仕組みとしてLilyPondアプリケーションのFrescobaldiで設定をいじる
- 別でより簡単にカタカナ全角で扱うためにちょっとしたGUIアプリケーションを作りました。
目標
「ドレミファミレド」みたいな入力だけで楽譜を作れるようにする。
手段1:LilyPond及びFrescobaldiを使う
環境
-
Lilypondをダウンロードする。
http://lilypond.org/index.ja.html -
Frescobaldiをダウンロードする。
https://github.com/frescobaldi/frescobaldi/releases
いずれも開発版で最新バージョンがおすすめです。
古いものでは私の環境では文字化けなどが発生しました。
- Lilypond 2.19.83
- Frescobaldi 3.0.0
書式を考える
警告対策のため、現状のLilypondのバージョンを明記します。
\version "2.19.83"
そのままでの入力はc d e f g a b
のようにアルファベット記法です。
個人的な好みとしてドレミで表現したいため、includeに下記を加えます。
\include "italiano.ly"
これで、do re mi fa sol la si
で音階が表現できます。
ソでsol
、ラをLはじまりのla
で表すことに気を付ければ直感的に表現が可能です。
フラットはb
を、シャープはd
を加えます。紛らわしいですが分別するコツとしては、フラットの形がb
に似ている。シャープはその逆でd
とします。
シンプルなテンプレートとしては下記が便利です。
\version "2.19.83"
\include "italiano.ly"
\score{
\new Staff{
\tempo 4 = 144
\relative do'{
do4. re8 mi4. do8
mi4 do mi2
}
}
\layout {}
\midi {}
}
おすすめの設定
-
Lilypond -> Automatic Engrave
にチェックを入れる。
編集時に自動で楽譜pdfとMIDIファイルを更新してくれます。 -
Tools
で頻繁に使用する機能を表示する。
簡単なMIDI確認などはTools->MIDI Player
で可能です。
備考
記法の詳細は 公式ドキュメント を参考に、do re mi
のような階名を中心に楽譜を作ることができました。
手段2:より直感的に扱うためアプリケーションを作成する。
こちらは少しやりすぎですが、追加で実装してみました。
環境
手段1にくわえ、GUIアプリケーションpyside(Qt For Python)
をインストールして、
「ドレミファ」など入力すると、自動的に楽譜を作成する手順までを実装しました。
(別にPysideにこだわる必要はありません。)
ねらい
Frescobaldiで作成しても、Lilypond記法の理解がある程度必要なのと、
プログラムになれてない人にも使えるように全角への対応をある程度よくしたい。
デモ
機能は大幅に絞られてますが、カタカナ全角入力に対応してすぐ楽譜にしていくサンプルです。
デモのように、ある程度その人にあわせた簡略化は考えられそうです。
(参考)作成したコード
#!/usr/bin/env python
import os
import sys
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtSvg import *
import subprocess
# 下記、環境に応じて変えてください。
cmd_path = 'C:\\Program Files (x86)\\LilyPond\\usr\\bin'
base_filepath = 'YOUR FILE PATH'
exe_lilypond_name = 'lilypond.exe'
code_dict = {'1':'1', '2':'2', '4':'4', '8':'8', '16':'16', '32':'32',
'ド': ' do', 'レ': ' re','ミ': ' mi', 'ファ': ' fa', 'ソ': ' sol','ラ': ' la', 'シ': ' si','ッ':' r',
'(':' <',')':'>','#':'d','♭':'b','↑':'\'','↓':','}
class UIWindow(QWidget):
def __init__(self):
super(UIWindow, self).__init__()
layout = QHBoxLayout()
w_code = QWidget()
w_code.setFixedWidth(500)
w_layout = QGridLayout()
self.chkbox_run = QCheckBox()
self.chkbox_run.setText("Run")
w_layout.addWidget(self.chkbox_run, 0, 0)
str_dict = ""
for i, c in enumerate(code_dict.items()):
str_dict += str(c) + ' '
if i % 4 == 3:
str_dict += "\n"
label_dict = QLabel()
label_dict.setText(str_dict)
w_layout.addWidget(label_dict, 2, 0)
self.edit_txt = QTextEdit()
w_layout.addWidget(self.edit_txt, 1, 0 )
self.txt_header = QTextEdit()
header_template = '''\
\\version "2.19.83"
\\include "italiano.ly"
\\score{
\\new Staff{
\\tempo 4 = 144
\\relative do'{
'''
self.txt_header.setText(header_template)
w_layout.addWidget(self.txt_header, 0 ,1 )
self.txt_code = QTextEdit()
w_layout.addWidget(self.txt_code, 1, 1)
self.txt_footer = QTextEdit()
footer_template = '''
}
}
\\layout { }
\\midi { }
}
'''
self.txt_footer.setText(footer_template)
w_layout.addWidget(self.txt_footer, 2, 1)
w_code.setLayout(w_layout)
layout.addWidget(w_code)
self.svg_gen = QSvgWidget()
self.svg_gen.setMinimumSize(QSize(400, 600))
self.svg_gen.setStyleSheet("background-color:white;")
layout.addWidget(self.svg_gen)
self.setLayout(layout)
def getKanaStr(self):
return self.edit_txt.toPlainText()
def setConvedStr(self, str_code):
self.txt_code.setText(str_code)
def pressOut(self):
full_str = self.txt_header.toPlainText() + \
self.txt_code.toPlainText() + \
self.txt_footer.toPlainText()
with open(f'{base_filepath}.ly', 'wt') as f:
f.write(full_str)
assert os.path.isdir(cmd_path)
os.chdir(cmd_path)
subprocess.Popen([exe_lilypond_name, '-dbackend=svg',f'-o{base_filepath}', f'{base_filepath}.ly'])
self.svg_gen.load(f'{base_filepath}.svg')
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.uiWindow = UIWindow()
self.setCentralWidget(self.uiWindow)
self.timer = QTimer()
self.timer.timeout.connect(self.run)
self.timer.start(5000)
self.old_kanastr = ""
def conv_txt(self, old_str):
new_str = ''
for index in range(len(old_str)):
for code_key in code_dict.keys():
if old_str[index:].startswith(code_key):
new_str += code_dict[code_key]
# 複数文字対策
if len(code_key) > 1:
index += len(code_key)-1
return new_str
def run(self):
if self.uiWindow.chkbox_run.isChecked():
self.new_kanastr = self.uiWindow.getKanaStr()
if self.new_kanastr != self.old_kanastr:
self.old_kanastr = self.new_kanastr
self.uiWindow.setConvedStr(self.conv_txt(self.new_kanastr))
self.uiWindow.pressOut()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())