2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LilyPondでテキストから楽譜を作るTips

Last updated at Posted at 2019-12-19

#まえがき

LilyPond及びそれを用いたライブラリやツールを使うと、
テキストで文字を書くことによって五線譜やMIDIファイルを作ることができます。
今回はLilypond周辺ツールを使ってより効果的に楽譜を作成することにフォーカスします。

2行で

  • 既存の仕組みとしてLilyPondアプリケーションのFrescobaldiで設定をいじる
  • 別でより簡単にカタカナ全角で扱うためにちょっとしたGUIアプリケーションを作りました。

目標

「ドレミファミレド」みたいな入力だけで楽譜を作れるようにする。

手段1:LilyPond及びFrescobaldiを使う

環境

いずれも開発版で最新バージョンがおすすめです。
古いものでは私の環境では文字化けなどが発生しました。

  • 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記法の理解がある程度必要なのと、
プログラムになれてない人にも使えるように全角への対応をある程度よくしたい。

デモ

機能は大幅に絞られてますが、カタカナ全角入力に対応してすぐ楽譜にしていくサンプルです。
scores2.gif

デモのように、ある程度その人にあわせた簡略化は考えられそうです。

(参考)作成したコード

#!/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_())

2
1
0

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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?