ハヌマーンで学ぶPython信号処理


##  はじめに

こちらの内容は、音楽データの分析および可視化に関するPythonコードを紹介する記事です。特に、バンド「ハヌマーン」の「アパルトの中の恋人達」の曲を例に、YouTubeから音声データをダウンロードし、音楽の特徴（テンポ、ピッチ、感情分析など）を抽出・解析する方法を実践します。


##  参考リンクまとめ


https://x.com/miyashin_prg/status/1911206204376044030?s=46

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/ad496cae-e528-4d25-8099-10f9c503c6d4.png)

https://www.youtube.com/watch?v=ahMb9c6jcRs



##  Pythonコード


```python
# 完全統合版：YouTube → MP3 → 可視化 → テンポ解析 → 構造解析 → ピッチ/感情マッピング

# --- Step 1: ライブラリのインストール（Colab再起動後用） ---
!pip install yt-dlp pydub librosa scikit-learn --quiet
!apt install ffmpeg -y

# --- Step 2: YouTubeからMP3をダウンロード ---
import yt_dlp
import os
from pydub import AudioSegment

# ダウンロードするYouTube動画のURL
url = "https://www.youtube.com/watch?v=ahMb9c6jcRs"  # 任意のYouTube動画URL

# 出力パス設定
output_path = "downloaded_audio.%(ext)s"

# yt-dlpの設定オプション
ydl_opts = {
    'format': 'bestaudio/best',  # 最も良い音質を選択
    'outtmpl': output_path,      # 出力ファイル名設定
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',    # MP3形式に変換
        'preferredcodec': 'mp3',        # MP3形式で保存
        'preferredquality': '192',      # 音質設定
    }],
    'quiet': True  # 出力を抑制
}

# YouTubeから音声をダウンロードしてMP3に変換
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    ydl.download([url])

# ダウンロードしたファイルの名前を変更
os.rename("downloaded_audio.mp3", "audio.mp3")
print("MP3変換完了")  # 成功メッセージ

# --- Step 3: 音声読み込みと可視化 ---
import librosa
import librosa.display
import matplotlib.pyplot as plt
import numpy as np

# 音声ファイルを読み込み
y, sr = librosa.load('audio.mp3')

# 波形を表示
plt.figure(figsize=(10, 6))
librosa.display.waveshow(y, sr=sr)
plt.title("Waveform of the Audio")
plt.xlabel("Time (s)")
plt.ylabel("Amplitude")
plt.show()

# --- Step 4: テンポ解析 ---
# テンポの推定
tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)

# テンポの表示
print(f"推定されたテンポ: {tempo} BPM")

# ビートの位置をプロット
plt.figure(figsize=(10, 6))
librosa.display.waveshow(y, sr=sr)
plt.vlines(librosa.frames_to_time(beat_frames), ymin=-1, ymax=1, color='r', alpha=0.7, label="Beats")
plt.title(f"Tempo Analysis: {tempo} BPM")
plt.xlabel("Time (s)")
plt.ylabel("Amplitude")
plt.legend()
plt.show()


# --- Step 5: ピッチ/感情マッピング ---
# ピッチを抽出
pitch, mag = librosa.core.piptrack(y=y, sr=sr)

# 最大ピッチを選択
index_max = mag[:, :].argmax(axis=0)
pitch_max = pitch[index_max, range(pitch.shape[1])]

# ピッチをプロット
plt.figure(figsize=(10, 6))
plt.plot(librosa.times_like(pitch_max), pitch_max, label='Pitch', color='g')
plt.title("Pitch Tracking")
plt.xlabel("Time (s)")
plt.ylabel("Frequency (Hz)")
plt.legend()
plt.show()

# 感情分析（仮にピッチとテンポに基づく簡単な感情マッピングを行う）
if tempo > 120 and np.mean(pitch_max) > 200:
    emotion = "Energetic"
elif tempo < 80 and np.mean(pitch_max) < 150:
    emotion = "Calm"
else:
    emotion = "Neutral"

print(f"推定される感情: {emotion}")

# --- Step 6: スペクトログラム解析 ---
# スペクトログラムを計算
D = librosa.amplitude_to_db(librosa.stft(y), ref=np.max)

# スペクトログラムの表示
plt.figure(figsize=(10, 6))
librosa.display.specshow(D, sr=sr, x_axis='time', y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Spectrogram')
plt.xlabel('Time (s)')
plt.ylabel('Frequency (Hz)')
plt.show()

# --- Step 7: コード認識 ---
# ピッチクラスを取得して、コードを認識する
chroma = librosa.feature.chroma_cens(y=y, sr=sr)

# コードをプロット
plt.figure(figsize=(10, 6))
librosa.display.specshow(chroma, x_axis='time', y_axis='chroma')
plt.colorbar(label='Chroma')
plt.title('Chroma Features (Chords)')
plt.xlabel('Time (s)')
plt.ylabel('Pitch Classes')
plt.show()
# --- Step 8: 和音と打楽器成分の分離 ---
# 音源分離（和音と打楽器）
harmonic, percussive = librosa.effects.hpss(y)

# 和音と打楽器の波形表示
plt.figure(figsize=(10, 6))
plt.subplot(2, 1, 1)
librosa.display.waveshow(harmonic, sr=sr)
plt.title("Harmonic Component")
plt.subplot(2, 1, 2)
librosa.display.waveshow(percussive, sr=sr)
plt.title("Percussive Component")
plt.tight_layout()
plt.show()
# --- Step 9: リズムとテンポの詳細分析 ---
# 発音（オンセット）の検出
onset_frames = librosa.onset.onset_detect(y=y, sr=sr)

# オンセットの位置をプロット
plt.figure(figsize=(10, 6))
librosa.display.waveshow(y, sr=sr)
plt.vlines(librosa.frames_to_time(onset_frames), ymin=-1, ymax=1, color='r', alpha=0.7, label="Onsets")
plt.title("Rhythm and Onset Detection")
plt.xlabel("Time (s)")
plt.ylabel("Amplitude")
plt.legend()
plt.show()
```

## 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/ba28306e-ec37-40bf-b055-a67e78472cf3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/9dd8f7ae-cfed-4c94-bcff-db7d9e889206.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/63e788f5-0c4c-48e1-b3e2-3754b50d2581.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/480818e7-7568-4113-9f1e-7743d5216664.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/1a9cf09f-8a15-45b9-833c-6beb1ea93fe5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/f507b1fa-15f8-4bbf-b8dd-953cab072508.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/89732f6e-8c59-4f40-87da-68a08c4689a2.png)