Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

Pythonで曲の楽器構成やAメロ・Bメロ・サビのタイムラインをプロット

やりたいこと

時系列信号・データを扱っていると、その信号に対する各種イベント・ラベル(例:どんな音が鳴っているか)を時間とともに表示したいと思うことが多々あります。

そこで、Pythonmatplotlib時間波形に対するイベント(ラベル)のタイムラインチャートを表示する方法を調べて実装してみました。

基礎編

plt.broken_barh(xranges, yrange, *, data=None, **kwargs) を使います。

この関数は、水平軸方向に同じy軸幅の矩形領域をいくつも描画できる関数です。xranges に各矩形領域の情報 (xmin, xwidth) の配列を与えます。yrange には(ymin, ywidth) を与えます((ドキュメントでは (ymin, ymax) となっている…))。

オプションとしては、例えば facecolors で色を指定したりできます。

f:id:Kurene:20191024171151p:plain
図1: タイムライン(チャート)プロット

import numpy as np
import matplotlib.pyplot as plt

labels = ["A", "B", "C"]
plt.clf()
# broken_barh( [(xmin, xwidth), (xmin, xwidth), ...], (ymin, ywidth))
plt.broken_barh([(0, 1.0)], (0, 1), facecolors='red')
plt.broken_barh([(0.5, 0.5), (1.5, 0.3)], (1, 1), facecolors='blue')
plt.broken_barh([(0.5, 1.2)], (2, 1), facecolors='green')
plt.ylim(0, 4)
plt.xlim(0, 2)
plt.title('Timeline')
plt.xlabel('Time')
plt.yticks(np.arange(0, 3)+0.5, labels)
plt.show()

応用編

楽曲に対して、トラック(楽器)、セクション(Aメロ、サビ、etc.)ラベルをタイムライン表示してみました。

今は時間区間情報を手動で打ち込んでいますが、自動で算出されたら便利な気がします。

f:id:Kurene:20191024164934p:plain
時間波形に対するイベントのタイムライン(中:楽器、下:セクション)

import librosa
import os
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib as mpl


filepath = "audio/Retrograde Amnesia.wav"
y, sr = librosa.load(filepath, mono=True)
duration = len(y) // sr

class Timeline:
    def __init__(self, data, label):
         # data = [(xmin, xwidth), (xmin, xwidth), ... ]
         self.data = [ (v[0], np.maximum(v[1]-v[0], 0)) for v in data]
         self.label = label

timeline_tracks = [
                    # Timeline(([xmin, xmax)], label_str, color)
                    Timeline([(0.8, 85)], "Drums"),
                    Timeline([(0.8, 85)], "Bass"),
                    Timeline([(0.8, 22.5), (36.5, 85)], "Dist. Gt. L"),
                    Timeline([(22.5, 36.5)], "Gt. L with Phaser"),
                    Timeline([(0.8, 85)], "Dist. Gt. R"),
                    Timeline([(22.5, 36.5), (43.5, 85)], "Vocal"),
                    Timeline([(8, 22.5), (36.5, 43.5)], "Lead Guitar"),
                   ]

timeline_sections= [
                    # Timeline(([xmin, xmax)], label_str, color)
                    Timeline([(22.5, 36.5), (43.5, 57)], "Verse"),
                    Timeline([(57, 61)], "Pre-Chorus"),
                    Timeline([(61, 85)], "Chorus"),
                    Timeline([(0.8, 22.5)], "Intro"),
                    Timeline([(36.5, 43.5)], "Interlude"),
                   ]

plt.clf()
mpl.rcParams['axes.xmargin'] = 0
mpl.rcParams['axes.ymargin'] = 0

plt.subplot(3,1,1)
plt.plot(np.linspace(0, duration, len(y)), np.sign(y)*(np.abs(y)**2), c="c")
plt.xlabel("Time (sec)")
plt.title(os.path.split(filepath)[1])

plt.subplot(3,1,2)
labels = []
for k, timeline in enumerate(timeline_tracks):
    plt.broken_barh(timeline.data, (k, 1), facecolors=cm.jet(k/len(timeline_tracks)))
    labels.append(timeline.label)
plt.ylim(0, len(timeline_tracks) )
plt.xlim(0, duration)
plt.xlabel('Time (sec)')
plt.yticks(np.arange(0, len(labels))+0.5, labels)
plt.title("Timeline of tracks")

plt.subplot(3,1,3)
labels = []
for k, timeline in enumerate(timeline_sections):
    plt.broken_barh(timeline.data, (k, 1), facecolors=cm.jet(k/len(timeline_sections)))
    labels.append(timeline.label)
plt.ylim(0, len(timeline_sections) )
plt.xlim(0, duration)
plt.xlabel('Time (sec)')
plt.yticks(np.arange(0, len(labels))+0.5, labels)
plt.title("Timeline of sections")

plt.tight_layout()
plt.show()

補足

使用楽曲は、幻象アリス:"Noctiflora"より "Retrograde Amnesia" (0:00-1:25) です。