1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

まえがき

LilyPond及びそれを用いたライブラリやツールを使うと、
テキストで文字を書くことによって五線譜やMIDIファイルを作ることができます。
今回はLilypond周辺ツールを使ってより効果的に楽譜を作成することにフォーカスします。
- こちらは、音楽ツール・ライブラリ・技術 Advent Calendar 2019 12/19分の記事です。

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_())

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?