Files
uj-mastering-master/master_core.py
T

247 lines
8.7 KiB
Python
Raw Normal View History

2024-04-10 20:22:59 +09:00
import librosa
import numpy as np
import os
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.cm as cm
from mutagen.mp3 import MP3
from mutagen.easyid3 import EasyID3
from font_manager import safe_title, initialize_fonts
2024-04-10 20:22:59 +09:00
def try_mp3_tags(file_path):
try:
# if there is metadata
audio = MP3(file_path, ID3=EasyID3)
return audio
except Exception as e:
print(f"Error reading ID3 tags: {e}")
return None
def read_mp3_tags(file_path):
if (audio := try_mp3_tags(file_path)) is not None:
print(f"File name: {safe_title(os.path.basename(file_path))}")
print(f"{safe_title(audio['artist'][0])} - {safe_title(audio['title'][0])}")
2024-04-10 20:22:59 +09:00
else:
print(f"File name: {safe_title(os.path.basename(file_path))}")
2024-09-25 10:35:07 +09:00
class AudioFile:
def __init__(self, file_path):
self.file_path = file_path
# file name / song name
if (audio := try_mp3_tags(self.file_path)) is not None:
self.song_name = safe_title(f"{audio['artist'][0]} - {audio['title'][0]}")
else:
self.song_name = safe_title(os.path.basename(self.file_path))
2024-09-25 10:35:07 +09:00
self.y, self.sr = librosa.load(file_path)
2024-09-25 13:48:01 +09:00
# load automatically normalises everything to [-1.0, 1.0]
# and that's alright
2024-09-25 10:35:07 +09:00
self.y_mono = librosa.to_mono(self.y)
2024-09-25 13:48:01 +09:00
self.max_amplitude = np.max(np.abs(self.y_mono))
self.avg_amplitude = np.mean(np.abs(self.y_mono))
self.bpm, _ = librosa.beat.beat_track(y=self.y_mono, sr=self.sr)
2024-09-25 13:48:01 +09:00
def display_song_name(self):
print(self.song_name)
2024-09-25 13:48:01 +09:00
def get_amplitudes(self):
return self.max_amplitude, self.avg_amplitude
def get_bpm(self):
# librosa.beat.beat_track returns numpy array - extract scalar value
if isinstance(self.bpm, np.ndarray):
return float(self.bpm[0]) if len(self.bpm) > 0 else 0.0
return float(self.bpm)
2024-09-25 13:48:01 +09:00
def get_energy_levels_over_time(self, window = 10, hop = 2):
2024-10-10 16:27:59 +09:00
"""_summary_
Args:
window (int, optional): Length of rolling RMS window in seconds. Defaults to 10.
hop (int, optional): Length of window hop in seconds. Defaults to 2.
"""
# check if the window and hop are the same as before
2024-10-11 14:23:56 +09:00
if (not hasattr(self, 'window')) or ((self.window != window) or (self.hop != hop)):
2024-10-10 16:27:59 +09:00
self.window, self.hop = window, hop
# only calculate if not already calculated
2024-10-11 14:23:56 +09:00
if not hasattr(self, 'rms_array'):
2024-10-10 16:27:59 +09:00
# window and hop are in seconds
window_samples = window * self.sr
hop_samples = hop * self.sr
# Calculate RMS over the rolling windows
2024-10-11 14:23:56 +09:00
self.rms_array = librosa.feature.rms(y=self.y, frame_length=window_samples, hop_length=hop_samples)
def _get_times(self):
"""Get time array for RMS data. Internal method for GUI integration."""
if not hasattr(self, 'rms_array'):
self.get_energy_levels_over_time()
return librosa.frames_to_time(np.arange(self.rms_array.shape[1]), sr=self.sr, hop_length=self.hop*self.sr)
2024-09-25 10:35:07 +09:00
2024-10-10 16:27:59 +09:00
def plot_energy_levels_over_time(self, display='window'):
"""_summary_
2024-09-25 13:48:01 +09:00
2024-10-10 16:27:59 +09:00
Args:
display (str, optional): Option for where to display the plot. Defaults to 'window'.
'window' - display in a pyplot window
'gui' - for directing to the GUI (TBD)
"""
2024-10-11 14:23:56 +09:00
if not hasattr(self, 'rms_array'):
2024-10-10 16:27:59 +09:00
self.get_energy_levels_over_time()
2024-10-11 14:23:56 +09:00
# Convert frame indices to time
times = librosa.frames_to_time(np.arange(self.rms_array.shape[1]), sr=self.sr, hop_length=self.hop*self.sr)
2024-10-10 16:27:59 +09:00
# Normalize RMS for color mapping
2024-10-24 18:49:00 +09:00
# check maximum power to determine mastering headspace:
# a -6 dBFS headroom should yield a max power of around 0.25
# otherwise could go anywhere, but we take 0.6
local_max_power = np.max(self.rms_array)
if local_max_power > 0.3:
2024-10-11 14:23:56 +09:00
norm = mcolors.Normalize(vmin=0, vmax=0.6)
maxpower = 0.6
else:
norm = mcolors.Normalize(vmin=0, vmax=0.3)
maxpower = 0.3
2024-10-10 16:27:59 +09:00
# colour map
cmap = cm.autumn
# Plot
if display == 'window':
fig, ax = plt.subplots(figsize=(10, 4))
2024-10-11 14:23:56 +09:00
ax.set_ylim(0., maxpower)
2024-10-10 16:27:59 +09:00
for i in range(len(times)-1):
2024-10-11 14:23:56 +09:00
ax.fill_between(times[i:i+2], 0, self.rms_array[0][i], color=cmap(norm(self.rms_array[0][i])), edgecolor='none')
2024-10-10 16:27:59 +09:00
# Adding a colorbar to indicate the scale of RMS values
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([])
cbar = plt.colorbar(sm, ax=ax, label='RMS Power')
# cbar.ax.set_yticklabels([f"{x-60.0:.0f} dBFS" for x in cbar.get_ticks()]) # Adjust labels to show true dBFS values
ax.set_ylabel('Power')
ax.set_xlabel('Time')
ax.set_title(safe_title(os.path.basename(self.file_path)))
2024-10-10 16:27:59 +09:00
plt.show(block=False)
plt.pause(0.001)
2024-09-25 13:48:01 +09:00
2024-04-10 20:22:59 +09:00
def analyze_track_librosa(file_path):
# Load the audio file
# y is the audio time series and sr is the sampling rate
y, sr = librosa.load(file_path)
# Calculate the maximum amplitude
# Librosa's load function normalizes the audio to [-1, 1], so we scale it back
max_amplitude = np.max(np.abs(y))
# Average amplitude
avg_amplitude = np.mean(np.abs(y))
# Convert max amplitude to dBFS
max_amplitude_dBFS = librosa.amplitude_to_db([max_amplitude], ref=1.0)
avg_amplitude_dBFS = librosa.amplitude_to_db([avg_amplitude], ref=1.0)
# Calculate RMS in dB
S, phase = librosa.magphase(librosa.stft(y))
rms_stft = librosa.feature.rms(S=S)
rms = librosa.feature.rms(y=y)
avg_power_dBFS_stft = 20 * np.log10(np.mean(rms_stft))
avg_power_dBFS = 20 * np.log10(np.mean(rms))
return max_amplitude_dBFS[0], avg_amplitude_dBFS[0], avg_power_dBFS, avg_power_dBFS_stft
def plot_macro_time_power_graph(file_path):
# Load the audio file
y, sr = librosa.load(file_path, mono=True)
# Define the window and hop length
# 10 seconds window and 1 second hop
window_length = int(sr * 10) # 10 seconds in samples
hop_length = int(sr * 1) # 1 second in samples
# Calculate RMS over the rolling windows
rms = librosa.feature.rms(y=y, frame_length=window_length, hop_length=hop_length)
# Convert frame indices to time
times = librosa.frames_to_time(np.arange(rms.shape[1]), sr=sr, hop_length=hop_length)
# Normalize RMS for color mapping
norm = mcolors.Normalize(vmin=0, vmax=0.4)
# Choose a colormap
cmap = cm.autumn
# Plot
fig, ax = plt.subplots(figsize=(10, 4))
ax.set_ylim(0., 0.4)
for i in range(len(times)-1):
ax.fill_between(times[i:i+2], 0, rms[0][i], color=cmap(norm(rms[0][i])), edgecolor='none')
# Adding a colorbar to indicate the scale of RMS values
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([])
cbar = plt.colorbar(sm, ax=ax, label='RMS Power')
# cbar.ax.set_yticklabels([f"{x-60.0:.0f} dBFS" for x in cbar.get_ticks()]) # Adjust labels to show true dBFS values
ax.set_ylabel('Power')
ax.set_xlabel('Time')
2024-10-23 01:07:16 +09:00
# ax.set_title(f'{os.path.basename(file_path)}')
2024-04-10 20:22:59 +09:00
# plt.ylabel('Power')
# plt.xlabel('Time (s)')
# plt.title(f'{os.path.basename(file_path)}')
plt.show(block=False)
plt.pause(0.001)
def find_mp3_files(directory):
mp3_files = []
# Walk through the directory
for root, dirs, files in os.walk(directory):
# Filter and append .mp3 files
for file in files:
if file.endswith(".mp3"):
mp3_files.append(os.path.join(root, file))
return mp3_files
if __name__ == '__main__':
# Legacy batch processing mode - runs when master_core.py is executed directly
# For GUI usage, run main.py instead
print("Running legacy batch analysis mode...")
print("For the new GUI interface, please run: python main.py")
print()
# Initialize fonts for matplotlib
initialize_fonts()
# Replace 'path/to/your/audiofile.mp3' with the path to your audio file
file_path = []
with open('./files.txt', 'r') as f:
for line in f:
if line[0] != '#' and line[0] != ';':
file_path.append(line.strip())
for file in file_path:
# max_amplitude, avg_amplitude, avg_power, avg_power_stft = analyze_track_librosa(file)
# # read_mp3_tags(file)
# print(f"Maximum Amplitude: {max_amplitude:.2f} dBFS")
# print(f"Average Amplitude: {avg_amplitude:.2f} dBFS")
# print(f"Average Power: {avg_power:.2f} dBFS")
# print(f"Average Power (STFT): {avg_power_stft:.2f} dBFS")
currentsong = AudioFile(file)
currentsong.display_song_name()
print(f"BPM: {currentsong.get_bpm()}")
currentsong.plot_energy_levels_over_time()
# plot_macro_time_power_graph(file)
plt.show()