# 从 full_song.py 的曲谱画简谱。Usage: python3 jianpu_all.py full_song.py 1|2 out.png import sys import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt from matplotlib import font_manager SRC, PART, OUTPNG = sys.argv[1], sys.argv[2], sys.argv[3] ns = {} _argv, sys.argv = sys.argv, ['x'] exec(open(SRC, encoding='utf-8').read().split('# 段落')[0], ns) sys.argv = _argv CJK = font_manager.FontProperties(family='Noto Sans CJK SC') CJK_B = font_manager.FontProperties(family='Noto Sans CJK SC', weight='bold') NUM = font_manager.FontProperties(family='DejaVu Sans') INK, SOFT, ACCENT = '#2b2a33', '#8a8794', '#b0567a' BEAT_W, BAR_PAD, LEFT = 1.0, 0.25, 0.6 BAR_W = 4 * BEAT_W + BAR_PAD H_OF = {'head': 0.65, 'bars': 1.3, 'note': 0.5} TITLE_H, DIGIT_FS, LYRIC_FS = 1.6, 19, 15 def pairs(bars): return [('bars', bars[i:i + 2]) for i in range(0, len(bars), 2)] if PART == '1': sub = '(前半首)' rows = [('head', '主歌一')] + pairs(ns['V1']) + [('head', '预副歌')] + pairs(ns['PC1']) + \ [('head', '副歌')] + pairs(ns['chorus']()) else: sub = '(后半首)' rows = [('head', '主歌二')] + pairs(ns['V2']) + [('head', '预副歌二')] + pairs(ns['PC2']) + \ [('head', '副歌二'), ('note', '同第一遍副歌。'), ('head', '桥段')] + pairs(ns['BRIDGE']) + \ [('note', '最后一小节是乐队的过门,不唱。'), ('head', '最后的副歌'), ('note', '前面同副歌,「唱得再远」改成「唱到很远」。最后一句换成:'), ('bars', ns['FINAL'][10:12]), ('bars', ns['FINAL'][12:13]), ('head', '尾声'), ('note', '人声停了,八音盒把副歌开头和「听见」两个音再弹一遍。')] W = LEFT + 2 * BAR_W + 0.5 H = TITLE_H + sum(H_OF[k] for k, _ in rows) + 0.7 fig = plt.figure(figsize=(W, H), dpi=120) fig.patch.set_facecolor('#fbf8f4') ax = fig.add_axes([0, 0, 1, 1]) ax.set_xlim(0, W); ax.set_ylim(0, H); ax.axis('off') y = H - 0.75 ax.text(W / 2, y, '当作听见', fontproperties=CJK_B, fontsize=26, color=INK, ha='center', va='center') y -= 0.55 ax.text(W / 2, y, sub, fontproperties=CJK, fontsize=12, color=SOFT, ha='center', va='center') y -= 0.45 ax.text(LEFT, y, '1 = D 4/4 ♩ = 76', fontproperties=NUM, fontsize=12, color=INK, ha='left', va='center') ax.text(W - 0.5, y, '词曲:Claude 唱:小月亮', fontproperties=CJK, fontsize=11, color=SOFT, ha='right', va='center') y -= TITLE_H - 1.75 def draw_bar(x0, yb, chord, notes, last): segs = chord if isinstance(chord, list) else [(chord, 4)] cx = x0 + 0.1 for cname, cb in segs: ax.text(cx, yb + 0.55, cname, fontproperties=NUM, fontsize=8.5, color=SOFT, ha='left', va='center') cx += cb * BEAT_W beat, groups = 0.0, {} for d, nb, lyric in notes: x = x0 + 0.35 + beat * BEAT_W ax.text(x, yb, d[0], fontproperties=NUM, fontsize=DIGIT_FS, color=INK, ha='center', va='center') if d.endswith("'"): ax.plot([x], [yb + 0.27], marker='o', markersize=2.6, color=INK) if d.endswith(','): ax.plot([x], [yb - 0.36], marker='o', markersize=2.6, color=INK) if lyric: ax.text(x, yb - 0.55, lyric, fontproperties=CJK, fontsize=LYRIC_FS, color=INK, ha='center', va='center') if nb == 0.5: groups.setdefault(int(beat), []).append(x) elif nb == 1.5: ax.plot([x + 0.2], [yb - 0.02], marker='o', markersize=2.6, color=INK) elif nb >= 2: for k in range(1, int(nb)): ax.text(x + k * BEAT_W, yb, '–', fontproperties=NUM, fontsize=DIGIT_FS, color=INK, ha='center', va='center') beat += nb for xs in groups.values(): ax.plot([min(xs) - 0.12, max(xs) + 0.12], [yb - 0.24, yb - 0.24], color=INK, lw=1.3, solid_capstyle='butt') xb = x0 + BAR_W ax.plot([xb, xb], [yb - 0.25, yb + 0.28], color=INK, lw=1.0) if last: ax.plot([xb + 0.07, xb + 0.07], [yb - 0.25, yb + 0.28], color=INK, lw=2.4) last_i = max(i for i, (k, _) in enumerate(rows) if k == 'bars') for i, (kind, payload) in enumerate(rows): y -= H_OF[kind] if kind == 'head': ax.text(LEFT - 0.25, y + 0.15, payload, fontproperties=CJK_B, fontsize=13, color=ACCENT, ha='left', va='center') elif kind == 'note': ax.text(LEFT - 0.05, y + 0.2, payload, fontproperties=CJK, fontsize=11.5, color=SOFT, ha='left', va='center') else: yb = y + 0.55 ax.plot([LEFT - 0.05, LEFT - 0.05], [yb - 0.25, yb + 0.28], color=INK, lw=1.0) for j, (chord, notes) in enumerate(payload): draw_bar(LEFT - 0.05 + j * BAR_W, yb, chord, notes, last=(i == last_i and j == len(payload) - 1)) fig.savefig(OUTPNG, facecolor=fig.get_facecolor()) print('saved', OUTPNG)