山内さん卒論_backup
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
検索
|
最終更新
|
ヘルプ
]
開始行:
[[技術資料]]
#Contents
**【目的】 [#c242d311]
メロディーのリアルタイム解析を通じて即興演奏や耳コピを支...
**【使用するファイル全部】 [#t4aaeb52]
#ref(111.png)
基本的な音楽知識と音楽理論,ギターの知識がないとプログラ...
#leaf_rotation=0がラベルの向き、orientation="top"がクラ...
#color_threshold=xでユークリッド平方距離がx以上を同色...
#above_threshold_color="color"でユークリッド平方距離...
##最新ラベルとラベルされているデータの距離が2000以内...
dendrogram(Z, labels=df_label,leaf_rotation='vertical...
#各データのクラスター番号出力
group = fcluster(Z, 2000, criterion='distance')
**【参考資料】 [#eda9093f]
音階度数(Degree):https://meloko-support.com/beginning/d...
周波数一覧:
https://www.petitmonte.com/javascript/musical_scale_frequ...
コード印象の違い:https://er-music.jp/theory/145/
マイナーコード構成:https://www.rakusta.jp/blog/code-minor
Amコード:https://guitar-concierge.com/chord/minor/a-minor/
リアルタイム判定:https://skimie.com/articles/6a3bfa82712...
広い音階:https://qiita.com/T1210Taichi/items/4daaeb9cec8...
**【ギターの解説】 [#h6c9cf1d]
ギターとは
ギターは、弦を振動させることによって音を出す楽器です。
ヴァイオリンやチェロのように弦を擦って音を出すのではなく...
学問的にはリュート属に分類されます。リュートとは、棹(さ...
演奏方法は、指板上のフレット(指板にある隆起)を指で押さ...
#ref(ギター.png)
・ヘッド
ギターの頭部で弦の端を保持する役割があります。ギターの種...
・ペグ
ヘッド部分に付いている部品で、弦を巻き取ることで弦にテン...
POINT
テンション:弦の張り具合のこと。
チューニング:弦の音の高さを合わせること。
・弦
音を鳴らすために欠かせないパーツです。弦はブリッジからペ...
種類は豊富で、素材とゲージ(太さ)によって音色や弾き心地...
・ナット
ヘッド付近で弦を支えるパーツです。弦高や音色を左右する重...
ナットも種類が多くあります。ギターは本体のみならずカスタ...
・ネック
ネックとは首のことで、ボディから長く伸びたパーツです。表...
弦のテンションに耐えうる硬い素材が使われています。
ネックは経年劣化や環境の変化などで反ってしまうことがあり...
・指板(フィンガーボード)
押弦する際に触るパーツです。硬い素材が使われています。
アコギでは平たい形状のもの、エレキでは丸みを帯びている形...
表面にはフレットと呼ばれる金属のパーツがついていて、指板...
・フレット
指板に固定されている隆起しているパーツです。フレットの箇...
・ポジションマーク
指板に付けられているマークのことです。このマークのお陰で...
3、5、7、9、12・・・と決まったフレットに付けられています。
12フレットはちょうど開放弦の音のオクターブ上のため、他の...
最低限の知識をつけてね~↓参考にしてね↓
ギターってどんな楽器?https://koujun.ac/music-theory-basi...
**【音楽理論の定義】 [#f1eaeb78]
***コードの音符を取得する [#s5b22b80]
class ChromaticScale:
notes = ('A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', '...
def __init__(self, root_note: str):
self.root_note = root_note
@property
def scale(self) -> dict[str, int]:
"""Generate a scale for a specific key."""
starting_idx = list(self.notes).index(self.root_n...
notes: list[str] = []
for idx in range(len(self.notes)):
new_index = (starting_idx + idx) % len(self.n...
notes.append(self.notes[new_index])
return dict(zip(notes, list(range(len(notes)))))
半音スケール、音程、さまざまな式を表すオブジェクトを追加...
***半音ごとに定義 [#oc9830c0]
class Intervals(Enum):
"""Standard intervals in western music in number of h...
P1 = 0 # perfect unison
m2 = 3 # minor second
M2 = 4 # major second
m3 = 3 # minor third
M3 = 4 # major third
P4 = 5 # perfect fourth
TT = 6 # tritone
d5 = 6 # diminished fifth
P5 = 7 # perfect fifth
m6 = 8 # minor sixth
M6 = 9 # major sixth
A5 = 8 # augmented 5
m7 = 10 # minor seventh
M7 = 11 # major seventh
P8 = 12 # octave
M9 = 14 # Major ninth
一般的なコードを定義する。
***コードごとの定義 [#w748a38d]
class ChordFormula(Enum):
major = Intervals.P1.value, Intervals.M3.value, Inter...
minor = Intervals.P1.value, Intervals.m3.value, Inter...
aug = Intervals.P1.value, Intervals.m3.value, Interva...
dim = Intervals.P1.value, Intervals.m3.value, Interva...
sus4 = Intervals.P1.value, Intervals.P4.value, Interv...
sus2 = Intervals.P1.value, Intervals.M2.value, Interv...
major7 = Intervals.P1.value, Intervals.M3.value, Inte...
dom7 = Intervals.P1.value, Intervals.M3.value, Interv...
minor7 = Intervals.P1.value, Intervals.m3.value, Inte...
minor7_flat5 = Intervals.P1.value, Intervals.m3.value...
dim7 = Intervals.P1.value, Intervals.m3.value, Interv...
major9 = Intervals.P1.value, Intervals.M3.value, Inte...
dom9 = Intervals.P1.value, Intervals.M3.value, Interv...
さまざまな音程があり、その中には同じ数の半音差を表すもの...
***フレットボード上で音符を見つける [#y36fdc2e]
from chromatic import ChromaticScale
class FretboardNotes:
chromatic_scale = ChromaticScale.notes
def __init__(
self,
tuning: tuple[str, ...] = ('E', 'B', 'G', 'D', 'A...
max_frets: int = 13
) -> None:
self._tuning = tuning
self._max_frets = max_frets
self._populate_all_strings()
def _populate_all_strings(self):
string_notes: dict[str, list[str]] = {}
for open_note in self._tuning:
string_notes[open_note] = []
for fret in range(self._max_frets + 1):
string_notes[open_note].append(
self.chromatic_scale[(self.chromatic_...
self.string_notes = string_notes
def get_note(self, string: int, fret: int) -> str:
open_note = self._tuning[string - 1]
return self.chromatic_scale[(self.chromatic_scale...
def get_fret_position_from_note(self, note: str):
positions: list[tuple[int, int]] = []
for string_no, open_note in enumerate(self.string...
frets = [idx for idx, i in enumerate(self.str...
positions.extend([(string_no + 1, f) for f in...
if string_no == 0:
positions.extend([(6, f) for f in frets])
return positions
_populate_all_stringsこのクラスには、初期化中に実行される...
最初のパブリック メソッドは、get_note弦番号とフレット番号...
***コードシェイプの取得 [#x3307a86]
class ChordShapes:
def __init__(
self,
tuning: tuple[str, ...] = ('E', 'B', 'G', 'D', 'A...
) -> None:
self.tuning = tuning
self.fretboard = FretboardNotes(tuning)
def parse_chord_string(self, chord_string: str) -> tu...
if len(chord_string) == 1:
return chord_string, "major"
if chord_string[1] in ["#", "♭", "b"]:
root_note = chord_string[:2]
raw_quality = chord_string[2:]
else:
root_note = chord_string[0]
raw_quality = chord_string[1:]
quality = self._parse_quality_string(raw_quality)
return root_note, quality
def _parse_quality_string(self, raw_quality: str) -> ...
if raw_quality == 'm':
return "minor"
elif raw_quality == 'm7':
return 'minor7'
else:
return raw_quality
def get_chord_notes(self, key: str, quality: str) -> ...
c = Chords(key, quality)
return c.notes
def get_chord_diagram(self, chord_string: str) -> lis...
key, quality = self.parse_chord_string(chord_stri...
raw_shapes = self._get_shapes(key, quality)
filtered_shapes = self._filter_shapes(raw_shapes)
return self._check_open_strings(filtered_shapes, ...
def _get_shapes(self, root: str, quality: str
) -> list[tuple[tuple[int, int], ...]...
# get chord notes translate flats to alternative ...
notes = [note_translator.get(n) or n
for n in self.get_chord_notes(root, qual...
chord_notes: dict[int, list[tuple[int, int]]] = {
idx: self.fretboard.get_fret_position_from_no...
for idx, n in enumerate(notes)
}
# now get all possible combinations
return list(product(*chord_notes.values()))
def _filter_shapes(self, raw_shapes: list[tuple[tuple...
# filter the resulting combinations such that:
# - notes of a chord are no more than 3 frets ap...
# - all notes have to be on separate strings
unplayable_idx: list[int] = []
fret_ranges = list(map(lambda x:
max(f[1] for f in x) - min(f[1] for f in x), ...
unplayable_idx = [idx for idx, i
in enumerate(fret_ranges) if i ...
# identify combinations where different
# notes are played on the same string
different_strings = [
idx for idx, i in enumerate(raw_shapes)
if len({x[0] for x in i}) != len(i)
]
unplayable_idx.extend(different_strings)
comb_filtered = [i for idx, i in enumerate(raw_sh...
if idx not in unplayable_idx]
# sort results in the order of strings
comb_filtered = [sorted(i, key=lambda x: x[0])
for i in comb_filtered]
return comb_filtered
多くの音符で 1 つの弦上に 2 つの異なる位置を設定できると...
音符の間隔は 4 フレットを超えてはいけません。そうしないと...
一度に発音できる音は 1 つだけであるため、コードのすべての...
ここでの主なメソッドは、get_chord_diagram()コード名の文字...
次に、メソッドを使用して生の運指形状を識別します_get_shap...
filter_shapes最後に、このセクションの冒頭に挙げた 2 つの...
***オープン弦とミュート弦の識別 [#we416003]
def _check_open_strings(
self,
raw_shapes: list[list[tuple[int, int]]],
root: str,
quality: str
) -> list[ChordDiagram]:
s = MusicScale(root)
scale = s.scale(ScalePatterns.__getitem__(quality...
print(f"{scale=}")
res: list[ChordDiagram] = []
for shape in raw_shapes:
# get root note string
root_string = self.fretboard.get_root_note_st...
played_strings: list[int] = [i[0] for i in sh...
raw_open_strings: list[int] = [i for i in ran...
if i not in pl...
# if any of the notes are played on 0th fret
# remove those from played strings and add to
# open strings.
raw_open_strings.extend([i[0] for i in shape ...
played_strings = [i[0] for i in shape if i[1]...
# if the open string is lower register than t...
# then it will need to get muted
muted_strings: list[int] = [i for i in raw_op...
if i > root_strin...
# go through tuning and if a note is not depr...
# it is still in the correct scale then add i...
open_strings = [idx+1 for idx, i in enumerate...
if i in scale
and idx+1 in raw_open_strings
and idx+1 not in muted_strings]
muted_strings = [i for i in raw_open_strings
if i not in open_strings]
res.append(ChordDiagram(
shape=shape,
open_strings=open_strings,
muted_strings=muted_strings
)
)
return res
事前にフィルタリングされたすべてのコード形状を確認し、そ...
場合によっては、コード スキーマに 0 フレットでフレットを...
次に、上で説明したように、提案された開放弦がルート音より...
次に、生成された音が正しいスケールでミュートされていない...
最後に、ChordDiagramオブジェクトを作成してリストに追加し...
#ref(gaga.jpg)
**【実装の流れ】 [#m51f4c2e]
1. ユーザーが演奏を行いたいコードの種類を選択しそれに合っ...
うにする.
2. 入力された音声をリアルタイムで取得し FFT を用い,音高...
3. 判定された音高に合わせ,ギターの指版に抑える場所を〇や...
成して画像としてプロットする.
4. 作成されたコード譜にあったタブ譜の情報を保存し共有でき...
***入力された音声の音高推定 [#g5835e23]
import matplotlib.pyplot as plt
import pyaudio as pa
import numpy as np
import cv2
from PIL import Image, ImageFont, ImageDraw
RATE=44100
BUFFER_SIZE=16384
HEIGHT=300
WIDTH=400
SCALE=[
'ラ', 'ラ#', 'シ', 'ド', 'ド#', 'レ',
'レ#', 'ミ', 'ファ', 'ファ#','ソ', 'ソ#'
]
## ストリーム準備
audio = pa.PyAudio()
stream = audio.open( rate=RATE,
channels=1,
format=pa.paInt16,
input=True,
frames_per_buffer=BUFFER_SIZE)
## 波形プロット用のバッファ
data_buffer = np.zeros(BUFFER_SIZE*16, int)
## 二つのプロットを並べて描画
fig = plt.figure()
fft_fig = fig.add_subplot(2,1,1)
wave_fig = fig.add_subplot(2,1,2)
while True:
try:
## ストリームからデータを取得
audio_data=stream.read(BUFFER_SIZE)
data=np.frombuffer(audio_data,dtype='int16')
fd = np.fft.fft(data)
fft_data = np.abs(fd[:BUFFER_SIZE//2])
freq=np.fft.fftfreq(BUFFER_SIZE, d=1/RATE)
## スペクトルで最大の成分を取得
val=freq[np.argmax(fft_data)]
offset = 0.5 if val >= 440 else -0.5
scale_num=int(np.log2((val/440.0)**12)+offset)%len(SCALE)
## 描画準備
canvas = np.full((HEIGHT,WIDTH,3),255,np.uint8)
## 日本語を描画するのは少し手間がかかる
### 自身の環境に合わせてフォントへのpathを指定する
font = ImageFont.truetype(
'/System/Library/Fonts/ヒラギノ角ゴシック W5.ttc',
120)
canvas = Image.fromarray(canvas)
draw = ImageDraw.Draw(canvas)
draw.text((20, 100),
SCALE[scale_num],
font=font,
fill=(0, 0, 0, 0))
canvas = np.array(canvas)
## 判定結果を描画
cv2.imshow('sample',canvas)
## プロット
data_buffer = np.append(data_buffer[BUFFER_SIZE:],data)
wave_fig.plot(data_buffer)
fft_fig.plot(freq[:BUFFER_SIZE//20],fft_data[:BUFFER_SI...
wave_fig.set_ylim(-10000,10000)
plt.pause(0.0001)
fft_fig.cla()
wave_fig.cla()
except KeyboardInterrupt: ## ctrl+c で終了
break
## 後始末
stream.stop_stream()
stream.close()
audio.terminate()
FFT (Fast Fourier Transform) を用いた周波数解析:
オーディオデータをFFTを使って周波数領域に変換します。
FFTを実行することで、波形データを周波数スペクトルに変換し...
周波数スペクトルの解析:
FFTを実行することで得られた周波数スペクトルから、特定の周...
最大振幅を持つ周波数成分を見つけ、その周波数を基に音高を...
周波数から音階の算出:
抽出された最大振幅の周波数を基に、音階を推定します。
音階は周波数に対して対応するように定義されており、440 Hz...
#ref(音高.png)
import matplotlib.pyplot as plt
import pyaudio as pa
import numpy as np
import cv2
import csv
from PIL import Image, ImageFont, ImageDraw
from core.plot_chords import ChordShapePlot
from core.chord_shapes import ChordDiagram, FingerPosition
from core.chord_name import ChordNameGenerator
# 定数の設定
RATE = 44100
BUFFER_SIZE = 16384
HEIGHT = 300
WIDTH = 400
SCALE = [
'A', 'A#', 'B', 'C', 'C#', 'D',
'D#', 'E', 'F', 'F#', 'G', 'G#'
]
# 音量の閾値を設定
VOLUME_THRESHOLD = 10000 # この値を調整して閾値を設定
# ストリームの準備
audio = pa.PyAudio()
stream = audio.open(rate=RATE,
channels=1,
format=pa.paInt16,
input=True,
frames_per_buffer=BUFFER_SIZE)
# 波形プロット用のバッファ
data_buffer = np.zeros(BUFFER_SIZE * 16, int)
# ウィンドウを表示するための関数
def show_window(image):
cv2.imshow('sample', image)
cv2.waitKey(1)
# ウィンドウを閉じるための関数
def close_window():
cv2.destroyAllWindows()
# 判定された音階を格納するリスト
HANTEI = []
# CSVファイルのヘッダー
csv_header = ['raw_open_strings', 'shape', 'played_string...
# ファイルを追記モードで開く
with open('chord_data.csv', 'a', newline='') as csvfile:
writer = csv.writer(csvfile)
# ヘッダーを書き込む(ファイルが空の場合のみ)
if csvfile.tell() == 0:
writer.writerow(csv_header)
current_chord = None # 現在表示中の和音
plot_index = 0
# 画像の保存先ディレクトリパスを指定
save_directory = r'C:\Users\tarku\Desktop\chord_visua...
while True:
try:
# ストリームからデータを取得
audio_data = stream.read(BUFFER_SIZE)
data = np.frombuffer(audio_data, dtype='int16')
# 音量の振幅を計算
amplitude = np.max(np.abs(data))
# 音量が閾値を超えた場合にのみ音階判定を実行
if amplitude > VOLUME_THRESHOLD:
fd = np.fft.fft(data)
fft_data = np.abs(fd[:BUFFER_SIZE // 2])
freq = np.fft.fftfreq(BUFFER_SIZE, d=1 / ...
# スペクトルで最大の成分を取得
val = freq[np.argmax(fft_data)]
offset = 0.5 if val >= 440 else -0.5
if val == 0:
scale_num = 0 # ゼロに近い場合、デフ...
else:
scale_num = int(np.log2((val / 440.0)...
HANTEI.append(SCALE[scale_num])
# 描画準備
canvas = np.full((HEIGHT, WIDTH, 3), 255,...
# 日本語を描画するのは少し手間がかかる
# 自身の環境に合わせてフォントへのpathを...
font = ImageFont.truetype('C:\\Windows\\F...
canvas = Image.fromarray(canvas)
draw = ImageDraw.Draw(canvas)
draw.text((20, 100),
SCALE[scale_num],
font=font,
fill=(0, 0, 0, 0))
canvas = np.array(canvas)
# 判定結果を描画
show_window(canvas)
data_buffer = np.append(data_buffer[BUFFE...
# 五回までの音階を使用してChordShapePlot...
if len(HANTEI) >= 8:
for i in range(len(HANTEI) - 8, len(HANTE...
if current_chord is not None:
plt.close() # 現在のウィンドウを...
current_chord = HANTEI[i]
try:
# 新しい和音ダイアグラムをプロット
cp = ChordShapePlot(current_chord...
cp.plot_by_idx(25)
# 新しい和音のデータを取得
raw_open_strings = cp.raw_open_st...
shape = cp.shape
played_strings = cp.played_strings
open_strings = cp.open_strings
muted_strings = cp.muted_strings
# CSVファイルにデータを書き込む
writer.writerow([raw_open_strings...
# 画像の保存先パスを指定
save_path = f'{save_directory}\\c...
# 新しい和音をプロットし、画像と...
plt.pause(2) # 2秒待
plt.savefig(save_path)
plt.close()
except Exception as e:
print(f"Error processing chord {c...
except KeyboardInterrupt: # ctrl+c で終了
close_window() # ウィンドウを閉じる
break
# 後処理
stream.stop_stream()
stream.close()
audio.terminate()
print("HANTEI:", HANTEI)
実際に実行するプログラム
終了行:
[[技術資料]]
#Contents
**【目的】 [#c242d311]
メロディーのリアルタイム解析を通じて即興演奏や耳コピを支...
**【使用するファイル全部】 [#t4aaeb52]
#ref(111.png)
基本的な音楽知識と音楽理論,ギターの知識がないとプログラ...
#leaf_rotation=0がラベルの向き、orientation="top"がクラ...
#color_threshold=xでユークリッド平方距離がx以上を同色...
#above_threshold_color="color"でユークリッド平方距離...
##最新ラベルとラベルされているデータの距離が2000以内...
dendrogram(Z, labels=df_label,leaf_rotation='vertical...
#各データのクラスター番号出力
group = fcluster(Z, 2000, criterion='distance')
**【参考資料】 [#eda9093f]
音階度数(Degree):https://meloko-support.com/beginning/d...
周波数一覧:
https://www.petitmonte.com/javascript/musical_scale_frequ...
コード印象の違い:https://er-music.jp/theory/145/
マイナーコード構成:https://www.rakusta.jp/blog/code-minor
Amコード:https://guitar-concierge.com/chord/minor/a-minor/
リアルタイム判定:https://skimie.com/articles/6a3bfa82712...
広い音階:https://qiita.com/T1210Taichi/items/4daaeb9cec8...
**【ギターの解説】 [#h6c9cf1d]
ギターとは
ギターは、弦を振動させることによって音を出す楽器です。
ヴァイオリンやチェロのように弦を擦って音を出すのではなく...
学問的にはリュート属に分類されます。リュートとは、棹(さ...
演奏方法は、指板上のフレット(指板にある隆起)を指で押さ...
#ref(ギター.png)
・ヘッド
ギターの頭部で弦の端を保持する役割があります。ギターの種...
・ペグ
ヘッド部分に付いている部品で、弦を巻き取ることで弦にテン...
POINT
テンション:弦の張り具合のこと。
チューニング:弦の音の高さを合わせること。
・弦
音を鳴らすために欠かせないパーツです。弦はブリッジからペ...
種類は豊富で、素材とゲージ(太さ)によって音色や弾き心地...
・ナット
ヘッド付近で弦を支えるパーツです。弦高や音色を左右する重...
ナットも種類が多くあります。ギターは本体のみならずカスタ...
・ネック
ネックとは首のことで、ボディから長く伸びたパーツです。表...
弦のテンションに耐えうる硬い素材が使われています。
ネックは経年劣化や環境の変化などで反ってしまうことがあり...
・指板(フィンガーボード)
押弦する際に触るパーツです。硬い素材が使われています。
アコギでは平たい形状のもの、エレキでは丸みを帯びている形...
表面にはフレットと呼ばれる金属のパーツがついていて、指板...
・フレット
指板に固定されている隆起しているパーツです。フレットの箇...
・ポジションマーク
指板に付けられているマークのことです。このマークのお陰で...
3、5、7、9、12・・・と決まったフレットに付けられています。
12フレットはちょうど開放弦の音のオクターブ上のため、他の...
最低限の知識をつけてね~↓参考にしてね↓
ギターってどんな楽器?https://koujun.ac/music-theory-basi...
**【音楽理論の定義】 [#f1eaeb78]
***コードの音符を取得する [#s5b22b80]
class ChromaticScale:
notes = ('A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', '...
def __init__(self, root_note: str):
self.root_note = root_note
@property
def scale(self) -> dict[str, int]:
"""Generate a scale for a specific key."""
starting_idx = list(self.notes).index(self.root_n...
notes: list[str] = []
for idx in range(len(self.notes)):
new_index = (starting_idx + idx) % len(self.n...
notes.append(self.notes[new_index])
return dict(zip(notes, list(range(len(notes)))))
半音スケール、音程、さまざまな式を表すオブジェクトを追加...
***半音ごとに定義 [#oc9830c0]
class Intervals(Enum):
"""Standard intervals in western music in number of h...
P1 = 0 # perfect unison
m2 = 3 # minor second
M2 = 4 # major second
m3 = 3 # minor third
M3 = 4 # major third
P4 = 5 # perfect fourth
TT = 6 # tritone
d5 = 6 # diminished fifth
P5 = 7 # perfect fifth
m6 = 8 # minor sixth
M6 = 9 # major sixth
A5 = 8 # augmented 5
m7 = 10 # minor seventh
M7 = 11 # major seventh
P8 = 12 # octave
M9 = 14 # Major ninth
一般的なコードを定義する。
***コードごとの定義 [#w748a38d]
class ChordFormula(Enum):
major = Intervals.P1.value, Intervals.M3.value, Inter...
minor = Intervals.P1.value, Intervals.m3.value, Inter...
aug = Intervals.P1.value, Intervals.m3.value, Interva...
dim = Intervals.P1.value, Intervals.m3.value, Interva...
sus4 = Intervals.P1.value, Intervals.P4.value, Interv...
sus2 = Intervals.P1.value, Intervals.M2.value, Interv...
major7 = Intervals.P1.value, Intervals.M3.value, Inte...
dom7 = Intervals.P1.value, Intervals.M3.value, Interv...
minor7 = Intervals.P1.value, Intervals.m3.value, Inte...
minor7_flat5 = Intervals.P1.value, Intervals.m3.value...
dim7 = Intervals.P1.value, Intervals.m3.value, Interv...
major9 = Intervals.P1.value, Intervals.M3.value, Inte...
dom9 = Intervals.P1.value, Intervals.M3.value, Interv...
さまざまな音程があり、その中には同じ数の半音差を表すもの...
***フレットボード上で音符を見つける [#y36fdc2e]
from chromatic import ChromaticScale
class FretboardNotes:
chromatic_scale = ChromaticScale.notes
def __init__(
self,
tuning: tuple[str, ...] = ('E', 'B', 'G', 'D', 'A...
max_frets: int = 13
) -> None:
self._tuning = tuning
self._max_frets = max_frets
self._populate_all_strings()
def _populate_all_strings(self):
string_notes: dict[str, list[str]] = {}
for open_note in self._tuning:
string_notes[open_note] = []
for fret in range(self._max_frets + 1):
string_notes[open_note].append(
self.chromatic_scale[(self.chromatic_...
self.string_notes = string_notes
def get_note(self, string: int, fret: int) -> str:
open_note = self._tuning[string - 1]
return self.chromatic_scale[(self.chromatic_scale...
def get_fret_position_from_note(self, note: str):
positions: list[tuple[int, int]] = []
for string_no, open_note in enumerate(self.string...
frets = [idx for idx, i in enumerate(self.str...
positions.extend([(string_no + 1, f) for f in...
if string_no == 0:
positions.extend([(6, f) for f in frets])
return positions
_populate_all_stringsこのクラスには、初期化中に実行される...
最初のパブリック メソッドは、get_note弦番号とフレット番号...
***コードシェイプの取得 [#x3307a86]
class ChordShapes:
def __init__(
self,
tuning: tuple[str, ...] = ('E', 'B', 'G', 'D', 'A...
) -> None:
self.tuning = tuning
self.fretboard = FretboardNotes(tuning)
def parse_chord_string(self, chord_string: str) -> tu...
if len(chord_string) == 1:
return chord_string, "major"
if chord_string[1] in ["#", "♭", "b"]:
root_note = chord_string[:2]
raw_quality = chord_string[2:]
else:
root_note = chord_string[0]
raw_quality = chord_string[1:]
quality = self._parse_quality_string(raw_quality)
return root_note, quality
def _parse_quality_string(self, raw_quality: str) -> ...
if raw_quality == 'm':
return "minor"
elif raw_quality == 'm7':
return 'minor7'
else:
return raw_quality
def get_chord_notes(self, key: str, quality: str) -> ...
c = Chords(key, quality)
return c.notes
def get_chord_diagram(self, chord_string: str) -> lis...
key, quality = self.parse_chord_string(chord_stri...
raw_shapes = self._get_shapes(key, quality)
filtered_shapes = self._filter_shapes(raw_shapes)
return self._check_open_strings(filtered_shapes, ...
def _get_shapes(self, root: str, quality: str
) -> list[tuple[tuple[int, int], ...]...
# get chord notes translate flats to alternative ...
notes = [note_translator.get(n) or n
for n in self.get_chord_notes(root, qual...
chord_notes: dict[int, list[tuple[int, int]]] = {
idx: self.fretboard.get_fret_position_from_no...
for idx, n in enumerate(notes)
}
# now get all possible combinations
return list(product(*chord_notes.values()))
def _filter_shapes(self, raw_shapes: list[tuple[tuple...
# filter the resulting combinations such that:
# - notes of a chord are no more than 3 frets ap...
# - all notes have to be on separate strings
unplayable_idx: list[int] = []
fret_ranges = list(map(lambda x:
max(f[1] for f in x) - min(f[1] for f in x), ...
unplayable_idx = [idx for idx, i
in enumerate(fret_ranges) if i ...
# identify combinations where different
# notes are played on the same string
different_strings = [
idx for idx, i in enumerate(raw_shapes)
if len({x[0] for x in i}) != len(i)
]
unplayable_idx.extend(different_strings)
comb_filtered = [i for idx, i in enumerate(raw_sh...
if idx not in unplayable_idx]
# sort results in the order of strings
comb_filtered = [sorted(i, key=lambda x: x[0])
for i in comb_filtered]
return comb_filtered
多くの音符で 1 つの弦上に 2 つの異なる位置を設定できると...
音符の間隔は 4 フレットを超えてはいけません。そうしないと...
一度に発音できる音は 1 つだけであるため、コードのすべての...
ここでの主なメソッドは、get_chord_diagram()コード名の文字...
次に、メソッドを使用して生の運指形状を識別します_get_shap...
filter_shapes最後に、このセクションの冒頭に挙げた 2 つの...
***オープン弦とミュート弦の識別 [#we416003]
def _check_open_strings(
self,
raw_shapes: list[list[tuple[int, int]]],
root: str,
quality: str
) -> list[ChordDiagram]:
s = MusicScale(root)
scale = s.scale(ScalePatterns.__getitem__(quality...
print(f"{scale=}")
res: list[ChordDiagram] = []
for shape in raw_shapes:
# get root note string
root_string = self.fretboard.get_root_note_st...
played_strings: list[int] = [i[0] for i in sh...
raw_open_strings: list[int] = [i for i in ran...
if i not in pl...
# if any of the notes are played on 0th fret
# remove those from played strings and add to
# open strings.
raw_open_strings.extend([i[0] for i in shape ...
played_strings = [i[0] for i in shape if i[1]...
# if the open string is lower register than t...
# then it will need to get muted
muted_strings: list[int] = [i for i in raw_op...
if i > root_strin...
# go through tuning and if a note is not depr...
# it is still in the correct scale then add i...
open_strings = [idx+1 for idx, i in enumerate...
if i in scale
and idx+1 in raw_open_strings
and idx+1 not in muted_strings]
muted_strings = [i for i in raw_open_strings
if i not in open_strings]
res.append(ChordDiagram(
shape=shape,
open_strings=open_strings,
muted_strings=muted_strings
)
)
return res
事前にフィルタリングされたすべてのコード形状を確認し、そ...
場合によっては、コード スキーマに 0 フレットでフレットを...
次に、上で説明したように、提案された開放弦がルート音より...
次に、生成された音が正しいスケールでミュートされていない...
最後に、ChordDiagramオブジェクトを作成してリストに追加し...
#ref(gaga.jpg)
**【実装の流れ】 [#m51f4c2e]
1. ユーザーが演奏を行いたいコードの種類を選択しそれに合っ...
うにする.
2. 入力された音声をリアルタイムで取得し FFT を用い,音高...
3. 判定された音高に合わせ,ギターの指版に抑える場所を〇や...
成して画像としてプロットする.
4. 作成されたコード譜にあったタブ譜の情報を保存し共有でき...
***入力された音声の音高推定 [#g5835e23]
import matplotlib.pyplot as plt
import pyaudio as pa
import numpy as np
import cv2
from PIL import Image, ImageFont, ImageDraw
RATE=44100
BUFFER_SIZE=16384
HEIGHT=300
WIDTH=400
SCALE=[
'ラ', 'ラ#', 'シ', 'ド', 'ド#', 'レ',
'レ#', 'ミ', 'ファ', 'ファ#','ソ', 'ソ#'
]
## ストリーム準備
audio = pa.PyAudio()
stream = audio.open( rate=RATE,
channels=1,
format=pa.paInt16,
input=True,
frames_per_buffer=BUFFER_SIZE)
## 波形プロット用のバッファ
data_buffer = np.zeros(BUFFER_SIZE*16, int)
## 二つのプロットを並べて描画
fig = plt.figure()
fft_fig = fig.add_subplot(2,1,1)
wave_fig = fig.add_subplot(2,1,2)
while True:
try:
## ストリームからデータを取得
audio_data=stream.read(BUFFER_SIZE)
data=np.frombuffer(audio_data,dtype='int16')
fd = np.fft.fft(data)
fft_data = np.abs(fd[:BUFFER_SIZE//2])
freq=np.fft.fftfreq(BUFFER_SIZE, d=1/RATE)
## スペクトルで最大の成分を取得
val=freq[np.argmax(fft_data)]
offset = 0.5 if val >= 440 else -0.5
scale_num=int(np.log2((val/440.0)**12)+offset)%len(SCALE)
## 描画準備
canvas = np.full((HEIGHT,WIDTH,3),255,np.uint8)
## 日本語を描画するのは少し手間がかかる
### 自身の環境に合わせてフォントへのpathを指定する
font = ImageFont.truetype(
'/System/Library/Fonts/ヒラギノ角ゴシック W5.ttc',
120)
canvas = Image.fromarray(canvas)
draw = ImageDraw.Draw(canvas)
draw.text((20, 100),
SCALE[scale_num],
font=font,
fill=(0, 0, 0, 0))
canvas = np.array(canvas)
## 判定結果を描画
cv2.imshow('sample',canvas)
## プロット
data_buffer = np.append(data_buffer[BUFFER_SIZE:],data)
wave_fig.plot(data_buffer)
fft_fig.plot(freq[:BUFFER_SIZE//20],fft_data[:BUFFER_SI...
wave_fig.set_ylim(-10000,10000)
plt.pause(0.0001)
fft_fig.cla()
wave_fig.cla()
except KeyboardInterrupt: ## ctrl+c で終了
break
## 後始末
stream.stop_stream()
stream.close()
audio.terminate()
FFT (Fast Fourier Transform) を用いた周波数解析:
オーディオデータをFFTを使って周波数領域に変換します。
FFTを実行することで、波形データを周波数スペクトルに変換し...
周波数スペクトルの解析:
FFTを実行することで得られた周波数スペクトルから、特定の周...
最大振幅を持つ周波数成分を見つけ、その周波数を基に音高を...
周波数から音階の算出:
抽出された最大振幅の周波数を基に、音階を推定します。
音階は周波数に対して対応するように定義されており、440 Hz...
#ref(音高.png)
import matplotlib.pyplot as plt
import pyaudio as pa
import numpy as np
import cv2
import csv
from PIL import Image, ImageFont, ImageDraw
from core.plot_chords import ChordShapePlot
from core.chord_shapes import ChordDiagram, FingerPosition
from core.chord_name import ChordNameGenerator
# 定数の設定
RATE = 44100
BUFFER_SIZE = 16384
HEIGHT = 300
WIDTH = 400
SCALE = [
'A', 'A#', 'B', 'C', 'C#', 'D',
'D#', 'E', 'F', 'F#', 'G', 'G#'
]
# 音量の閾値を設定
VOLUME_THRESHOLD = 10000 # この値を調整して閾値を設定
# ストリームの準備
audio = pa.PyAudio()
stream = audio.open(rate=RATE,
channels=1,
format=pa.paInt16,
input=True,
frames_per_buffer=BUFFER_SIZE)
# 波形プロット用のバッファ
data_buffer = np.zeros(BUFFER_SIZE * 16, int)
# ウィンドウを表示するための関数
def show_window(image):
cv2.imshow('sample', image)
cv2.waitKey(1)
# ウィンドウを閉じるための関数
def close_window():
cv2.destroyAllWindows()
# 判定された音階を格納するリスト
HANTEI = []
# CSVファイルのヘッダー
csv_header = ['raw_open_strings', 'shape', 'played_string...
# ファイルを追記モードで開く
with open('chord_data.csv', 'a', newline='') as csvfile:
writer = csv.writer(csvfile)
# ヘッダーを書き込む(ファイルが空の場合のみ)
if csvfile.tell() == 0:
writer.writerow(csv_header)
current_chord = None # 現在表示中の和音
plot_index = 0
# 画像の保存先ディレクトリパスを指定
save_directory = r'C:\Users\tarku\Desktop\chord_visua...
while True:
try:
# ストリームからデータを取得
audio_data = stream.read(BUFFER_SIZE)
data = np.frombuffer(audio_data, dtype='int16')
# 音量の振幅を計算
amplitude = np.max(np.abs(data))
# 音量が閾値を超えた場合にのみ音階判定を実行
if amplitude > VOLUME_THRESHOLD:
fd = np.fft.fft(data)
fft_data = np.abs(fd[:BUFFER_SIZE // 2])
freq = np.fft.fftfreq(BUFFER_SIZE, d=1 / ...
# スペクトルで最大の成分を取得
val = freq[np.argmax(fft_data)]
offset = 0.5 if val >= 440 else -0.5
if val == 0:
scale_num = 0 # ゼロに近い場合、デフ...
else:
scale_num = int(np.log2((val / 440.0)...
HANTEI.append(SCALE[scale_num])
# 描画準備
canvas = np.full((HEIGHT, WIDTH, 3), 255,...
# 日本語を描画するのは少し手間がかかる
# 自身の環境に合わせてフォントへのpathを...
font = ImageFont.truetype('C:\\Windows\\F...
canvas = Image.fromarray(canvas)
draw = ImageDraw.Draw(canvas)
draw.text((20, 100),
SCALE[scale_num],
font=font,
fill=(0, 0, 0, 0))
canvas = np.array(canvas)
# 判定結果を描画
show_window(canvas)
data_buffer = np.append(data_buffer[BUFFE...
# 五回までの音階を使用してChordShapePlot...
if len(HANTEI) >= 8:
for i in range(len(HANTEI) - 8, len(HANTE...
if current_chord is not None:
plt.close() # 現在のウィンドウを...
current_chord = HANTEI[i]
try:
# 新しい和音ダイアグラムをプロット
cp = ChordShapePlot(current_chord...
cp.plot_by_idx(25)
# 新しい和音のデータを取得
raw_open_strings = cp.raw_open_st...
shape = cp.shape
played_strings = cp.played_strings
open_strings = cp.open_strings
muted_strings = cp.muted_strings
# CSVファイルにデータを書き込む
writer.writerow([raw_open_strings...
# 画像の保存先パスを指定
save_path = f'{save_directory}\\c...
# 新しい和音をプロットし、画像と...
plt.pause(2) # 2秒待
plt.savefig(save_path)
plt.close()
except Exception as e:
print(f"Error processing chord {c...
except KeyboardInterrupt: # ctrl+c で終了
close_window() # ウィンドウを閉じる
break
# 後処理
stream.stop_stream()
stream.close()
audio.terminate()
print("HANTEI:", HANTEI)
実際に実行するプログラム
ページ名: