コード進行のデータから動画を作る

投稿日:2024-07-24
更新日:2024-11-07

ジャンル:動画


概要

前に考えた独自の音楽理論 #てぃみ式 を基にコードのデータから動画を作るプログラム

例:Sazupinyavi (ピアノロールはMIDITrailをBBで録画して透過しただけ)

図形の意味


用意するデータ (json)

スケール(調と変位)・ベース音・和音の形が決まればコードが決まるのでこれらのデータを詰め込んでいくイメージ

直で書くの大変だからエディタみたいなのも欲しい (前に作ったのをちょっと改造すればできそう)
→作った!!

初めに指定するもの(任意)
  • default_beats: number; (beatsを指定しない場合のbeatsの値)
  • bgcolor: string; (背景色)
  • color: string; (図形の色)
codes内にコードの情報を順番に書いていく

keyとbpmは一番最初と変更がある箇所だけでok、「?」つきののもは任意

  • key?: number; #の数(+)/♭の数(-)で指定 (例: ♭3つ → -3)
  • bpm?: number;
  • bass: number; ベース音 (ルート音を基準に1~7) (例: GMajのC → 4)
  • shape: string; 和音の形 (ベース音を基準に構成音を1~7の数字で並べる) (例: ドファソ → "145")
  • accd?: number[]; 変位 (ルート音を基準に1~7の数字、#は+、♭は-) (例: 1が#, 3が♭ → [1, -3])
  • beats?: number; 拍数 (default_beatsでないときだけでok)
具体例

実際に使ったデータから抜粋 (accdが空のとこはaccd無くてもいい)


{
	"default_beats": 2,
	"bgcolor": "#ffffff",
	"color": "#88ccee",
	"chords": [
		{
			"memo": "---------A(2)----------",
			"key": -2,
			"bpm": 176,
			"bass": 4,
			"shape": "1357",
			"accd": []
		},
		{
			"bass": 7,
			"shape": "1357",
			"accd": []
		},
		{
			"bass": 3,
			"shape": "1357",
			"accd": []
		},
		{
			"bass": 6,
			"shape": "1357",
			"accd": [1]
		},
		{
			"bass": 2,
			"shape": "1357",
			"accd": []
		},
		{
			"bass": 5,
			"shape": "1357",
			"accd": []
		},
		{
			"bass": 1,
			"shape": "135",
			"accd": [],
			"beats": 1
		},
		{
			"bass": 5,
			"shape": "146",
			"accd": [],
			"beats": 1
		},
		{
			"bass": 3,
			"shape": "1357",
			"accd": [],
			"beats": 1
		},
		{
			"bass": 3,
			"shape": "1357",
			"accd": [-3, 5],
			"beats": 1
		},
		{
			"bass": 2,
			"shape": "1357",
			"accd": []
		},
		...
	]
}

動画を生成するコード (py)

import json
import math
from PIL import Image, ImageDraw, ImageFont	# pip install Pillow
import cv2	# pip install opencv-python

# basからdeg度上の音 (毎回型変換するのがめんどいので関数化)
def upper(bas, deg):
	return str((int(bas) + deg - 1) % 7 + 1)

# 円の中心, 半径, 割合 から座標を求める (デザイン依存度高め)
def get_xy(center, r, per):
		x, y = center
		x += r * math.sin(2 * math.pi * per)
		y -= r * math.cos(2 * math.pi * per)
		return x, y


# shapeから各構成音の重みを計算 (#てぃみ式3)
def calc_weight(shape):
	weights = []
	for s in shape:
		pts = 0
		if s == "1":
			pts += 2
		if upper(s, 2) in shape:
			pts += 2
		if upper(s, 4) in shape:
			pts += 3
		if upper(s, 6) in shape:
			pts += 1
		weights.append(pts)
	return weights


# コードの情報から画像(のもとになるデータ)を生成
def make_image(key, bass, shape, accd, bgcolor, color, size):
	width, height = size

	# 図形配置の基準を設定 (デザイン依存度高)
	left_center = (width / 2 - 250, height / 2 + 80)
	right_center = (width / 2 + 250, height / 2 + 80)
	left_r = 160
	right_r = 100
	dot_size = 30

	# weightsで透過させるためにalphaと分離
	image = Image.new("RGBA", (width, height), bgcolor)
	draw = ImageDraw.Draw(image)
	image_alpha = Image.new("RGBA", (width, height), "#00000000")
	draw_alpha = ImageDraw.Draw(image_alpha)

	# データからコードを解析 (表示用のデータに変換)
	# keyとaccdからスケールの五度圏表示用データ
	# 0, 1, 2, ... -> C, G, D, ...
	scale = [i + key for i in [0, 2, 4, -1, 1, 3, 5]]
	for a in accd:
		if a > 0:
			scale[a - 1] += 7
		else:
			scale[abs(a) - 1] -= 7

	# bassとshapeから和音の三度圏表示用データ
	# 0, 1, 2, ... -> C, E, G, ...
	root = 2 * key
	bass_note = root - 3 * (bass - 1)
	notes = [root - 3 * (bass - 1 + int(s) - 1) for s in shape]
	weights = calc_weight(shape)

	# 上のデータを基に図形描画 (デザイン依存度高)
	for i in range(12):
		x, y = get_xy(left_center, left_r, i / 12)
		draw_alpha.circle((x, y), dot_size, fill=color+"33")

	for s in scale:
		x, y = get_xy(left_center, left_r, s / 12)
		draw.circle((x, y), dot_size, fill=color)

	x, y = get_xy(right_center, right_r, bass_note / 7)
	draw.circle((x, y), dot_size + 10, outline=color, width=5, fill=bgcolor)

	for i, n in enumerate(notes):
		x, y = get_xy(right_center, right_r, n / 7)
		draw_alpha.circle((x, y), dot_size, fill=color+f"{int(255 * (weights[i] + 4) / 12):02x}")

	# 透過用のalphaと結合
	image = Image.alpha_composite(image, image_alpha)
	return image


# データ読込→動画出力
def read_data(path, filename, size):
	with open(path, "r") as fp:
		data = json.load(fp)
	bgcolor = data.get("bgcolor", "#ffffff")
	color = data.get("color", "#aaaaaa")
	default_beats = data.get("default_beats", 2)
	fps = 30
	sec_total = 0
	frame_total = 0
	key = 0
	bpm = 160

	# 動画生成関連
	fourcc = cv2.VideoWriter_fourcc("m", "p", "4", "v")
	video = cv2.VideoWriter(filename, fourcc, fps, size)
	# temp_path = "temp.png"	# 一時的に画像を保存する場所なので適当なパスに変更してok ←これ直で変換できたんだ

	# chordsの中身から動画作っていく
	for chord in data["chords"]:
		if "key" in chord:
			key = chord["key"]
		if "bpm" in chord:
			bpm = chord["bpm"]
		bass = chord["bass"]
		shape = chord["shape"]
		accd = chord.get("accd", [])
		image = make_image(key, bass, shape, accd, bgcolor, color, size)

		# frame数を計算
		beats = chord.get("beats", default_beats)
		sec = beats * 60 / bpm
		sec_total += sec
		frame = int(sec_total * fps - frame_total)
		frame_total += frame

		# image.save(temp_path)
		# video_img = cv2.imread(temp_path)
		# ↓の方法で直で変換できた
		image_np = np.array(image, dtype=np.uint8)
		video_img = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
		for i in range(frame):
			video.write(video_img)
	video.release()


# 読み込むjsonのパス, 保存するファイル名, サイズ
read_data("169.json", "chord_movie.mp4", (1280, 720))

←前へ一覧へ 次へ→

Tweet