Skip to content

Commit c02090a

Browse files
committed
Replace minor libfmp helpers locally
1 parent 52574f3 commit c02090a

12 files changed

Lines changed: 265 additions & 26 deletions

sync_audio_audio_full.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@
3434
"import matplotlib.pyplot as plt\n",
3535
"import IPython.display as ipd\n",
3636
"import scipy.interpolate\n",
37-
"from libfmp.b.b_plot import plot_signal, plot_chromagram\n",
38-
"from libfmp.c3.c3s2_dtw_plot import plot_matrix_with_points\n",
37+
"from synctoolbox.feature.visualization import plot_chromagram, plot_matrix_with_points, plot_signal\n",
3938
"\n",
4039
"from synctoolbox.dtw.mrmsdtw import sync_via_mrmsdtw\n",
4140
"from synctoolbox.dtw.utils import compute_optimal_chroma_shift, shift_chroma_vectors, make_path_strictly_monotonic, evaluate_synchronized_positions\n",
@@ -123,7 +122,6 @@
123122
},
124123
"outputs": [],
125124
"source": [
126-
"import libfmp.c2\n",
127125
"# Alternative: librosa.estimate_tuning\n",
128126
"tuning_offset_1 = estimate_tuning(audio_1, Fs)\n",
129127
"tuning_offset_2 = estimate_tuning(audio_2, Fs)\n",

sync_audio_audio_simple.ipynb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
"import librosa.display\n",
3131
"import matplotlib.pyplot as plt\n",
3232
"import IPython.display as ipd\n",
33-
"from libfmp.b.b_plot import plot_signal, plot_chromagram\n",
34-
"from libfmp.c3.c3s2_dtw_plot import plot_matrix_with_points\n",
33+
"from synctoolbox.feature.visualization import plot_chromagram, plot_matrix_with_points, plot_signal\n",
3534
"\n",
3635
"from synctoolbox.dtw.core import compute_warping_path\n",
3736
"from synctoolbox.dtw.cost import cosine_distance\n",

sync_audio_score_full.ipynb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
"source": [
3030
"# Loading some modules and defining some constants used later\n",
3131
"import IPython.display as ipd\n",
32-
"from libfmp.b import list_to_pitch_activations, plot_chromagram, plot_signal, plot_matrix, \\\n",
33-
" sonify_pitch_activations_with_signal\n",
32+
"from synctoolbox.feature.sonification import list_to_pitch_activations, sonify_pitch_activations_with_signal\n",
33+
"from synctoolbox.feature.visualization import plot_chromagram, plot_matrix, plot_signal\n",
3434
"import librosa.display\n",
3535
"import matplotlib.pyplot as plt\n",
3636
"import numpy as np\n",
@@ -119,7 +119,6 @@
119119
},
120120
"outputs": [],
121121
"source": [
122-
"import libfmp.c2\n",
123122
"# Alternative: librosa.estimate_tuning\n",
124123
"tuning_offset = estimate_tuning(audio, Fs)\n",
125124
"print('Estimated tuning deviation for recording: %d cents' % (tuning_offset))"

synctoolbox/dtw/utils.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import numpy as np
2-
from libfmp.c3 import compute_strict_alignment_path_mask
32
from typing import List
43

54
from synctoolbox.dtw.core import compute_warping_path
@@ -243,8 +242,6 @@ def find_anchor_indices_in_warping_path(warping_path: np.ndarray,
243242
def make_path_strictly_monotonic(P: np.ndarray) -> np.ndarray:
244243
"""Compute strict alignment path from a warping path
245244
246-
Wrapper around "compute_strict_alignment_path_mask" from libfmp.
247-
248245
Parameters
249246
----------
250247
P: np.ndarray [shape=(2, N)]
@@ -255,9 +252,14 @@ def make_path_strictly_monotonic(P: np.ndarray) -> np.ndarray:
255252
P_mod: np.ndarray [shape=(2, M)]
256253
Strict alignment path, M <= N
257254
"""
258-
P_mod = compute_strict_alignment_path_mask(P.T)
259-
260-
return P_mod.T
255+
P_transposed = np.array(P.T, copy=True)
256+
N, M = P_transposed[-1]
257+
keep_mask = (P_transposed[1:, 0] > P_transposed[:-1, 0]) & (P_transposed[1:, 1] > P_transposed[:-1, 1])
258+
keep_mask = np.concatenate(([True], keep_mask))
259+
keep_mask[(P_transposed[:, 0] == N) | (P_transposed[:, 1] == M)] = False
260+
keep_mask[-1] = True
261+
262+
return P_transposed[keep_mask, :].T
261263

262264

263265
def evaluate_synchronized_positions(ground_truth_positions: np.ndarray,

synctoolbox/feature/csv_tools.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import csv
12
import os.path
23
import subprocess
34

4-
import libfmp.c1
55
import music21
66
import numpy as np
77
import pandas as pd
@@ -343,7 +343,7 @@ def __get_audio_duration_from_df(df: pd.DataFrame) -> float:
343343

344344
def music_xml_to_csv_musical_time(xml, csv_filepath: str):
345345
"""Convert a music xml file to a list of note events, with starts and durations as fractions of measures, and stores
346-
it as a csv file in libfmp format.
346+
it as a csv file.
347347
348348
Args:
349349
xml (str or music21.stream.Score): Either a path to a music xml file or a music21.stream.Score
@@ -367,7 +367,7 @@ def music_xml_to_csv_musical_time(xml, csv_filepath: str):
367367
xml_data = xml_data_expanded
368368

369369
score = []
370-
# First, get starts and ends of notes in terms of quarters (similar to 'xml_to_list' in libfmp)
370+
# First, get starts and ends of notes in terms of quarters.
371371
for part in xml_data.parts:
372372
instrument = __get_part_instrument_name(part)
373373
for note in part.flatten().notes:
@@ -419,13 +419,14 @@ def get_position_in_fraction_of_measures(position_in_quarters, is_end=False):
419419
for i in range(len(score)):
420420
start = get_position_in_fraction_of_measures(score[i][0])
421421
end = get_position_in_fraction_of_measures(score[i][1], is_end=True)
422-
# To stay compatible with libfmp functions, we store this as a list with start and duration,
422+
# Store this as a list with start and duration,
423423
# although many of our annotations are actually stored as start and end...
424424
score[i][0] = start
425425
score[i][1] = end - start
426426

427427
score = sorted(score, key=lambda x: (x[0], x[2]))
428-
libfmp.c1.list_to_csv(score, csv_filepath)
428+
df = pd.DataFrame(score, columns=['Start', 'Duration', 'Pitch', 'Velocity', 'Instrument'])
429+
df.to_csv(csv_filepath, sep=';', index=False, quoting=csv.QUOTE_NONNUMERIC)
429430

430431

431432
def midi_to_music_xml_musescore(midi_filepath: str, musescore_executable: str = "musescore"):

synctoolbox/feature/dlnco.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import matplotlib.pyplot as plt
22
import numpy as np
3-
from libfmp.b import MultiplePlotsWithColorbar, plot_chromagram, plot_matrix
3+
4+
from synctoolbox.feature.visualization import plot_chromagram
45

56

67
def pitch_onset_features_to_DLNCO(f_peaks: dict,

synctoolbox/feature/novelty.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import librosa
2-
from libfmp.c6 import compute_local_average
32
import numpy as np
43

54

@@ -59,7 +58,7 @@ def spectral_flux(f_audio: np.ndarray,
5958

6059
# Compute local average
6160
M = int(np.ceil(M_sec * Fs / hop_size))
62-
local_average = compute_local_average(nov, M)
61+
local_average = __compute_local_average(nov, M)
6362

6463
# Subtract the local average from the novelty curve
6564
nov_norm = nov - local_average
@@ -68,6 +67,18 @@ def spectral_flux(f_audio: np.ndarray,
6867
return nov_norm
6968

7069

70+
def __compute_local_average(x: np.ndarray, M: int) -> np.ndarray:
71+
"""Compute a centered local average with a fixed window denominator."""
72+
L = len(x)
73+
prefix_sum = np.concatenate(([0], np.cumsum(x)))
74+
local_average = np.zeros(L)
75+
for m in range(L):
76+
a = max(m - M, 0)
77+
b = min(m + M + 1, L)
78+
local_average[m] = (prefix_sum[b] - prefix_sum[a]) / (2 * M + 1)
79+
return local_average
80+
81+
7182
def add_decay(nov_norm: np.ndarray,
7283
filter_coeff: np.ndarray = np.sqrt(1 / np.arange(1, 11))):
7384
# Add a temporal decay to the novelty curve.

synctoolbox/feature/pitch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from libfmp.b import plot_matrix
21
import numpy as np
32
import matplotlib.pyplot as plt
43
from scipy import signal
54

65
from synctoolbox.feature.filterbank import FS_PITCH, generate_list_of_downsampled_audio, get_fs_index,\
76
generate_filterbank
7+
from synctoolbox.feature.visualization import plot_matrix
88

99
PITCH_NAME_LABELS = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
1010
'C0 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import numpy as np
2+
3+
4+
def list_to_pitch_activations(note_list, num_frames, frame_rate):
5+
"""Create a pitch activation matrix from a list of note events."""
6+
P = np.zeros((128, num_frames))
7+
F_coef_MIDI = np.arange(128) + 1
8+
for note in note_list:
9+
start_frame = max(0, int(note[0] * frame_rate))
10+
end_frame = min(num_frames, int((note[0] + note[1]) * frame_rate) + 1)
11+
P[int(note[2] - 1), start_frame:end_frame] = 1
12+
return P, F_coef_MIDI
13+
14+
15+
def sonify_pitch_activations(P,
16+
N,
17+
frame_rate,
18+
Fs,
19+
min_pitch=1,
20+
Fc=440,
21+
harmonics_weights=(1,),
22+
fading_msec=5):
23+
"""Sonify a pitch activation matrix using sinusoidal tones."""
24+
fade_sample = int(fading_msec / 1000 * Fs)
25+
pitch_son = np.zeros((N,))
26+
27+
for p in range(P.shape[0]):
28+
if np.sum(np.abs(P[p, :])) > 0:
29+
pitch = min_pitch + p
30+
freq = (2 ** ((pitch - 69) / 12)) * Fc
31+
sin_tone = np.zeros((N,))
32+
33+
for i, cur_harmonic_weight in enumerate(harmonics_weights):
34+
sin_tone += cur_harmonic_weight * np.sin(2 * np.pi * (i + 1) * freq * np.arange(N) / Fs)
35+
36+
weights = np.zeros((N,))
37+
for n in range(P.shape[1]):
38+
if np.abs(P[p, n]) > 0:
39+
start = min(N, max(0, int((n - 0.5) * Fs / frame_rate)))
40+
end = min(N, int((n + 0.5) * Fs / frame_rate))
41+
fade_start = min(N, start + fade_sample)
42+
fade_end = min(N, end + fade_sample)
43+
44+
weights[fade_start:end] += P[p, n]
45+
weights[start:fade_start] += np.linspace(0, P[p, n], fade_start - start)
46+
weights[end:fade_end] += np.linspace(P[p, n], 0, fade_end - end)
47+
48+
pitch_son += weights * sin_tone
49+
50+
pitch_son = pitch_son / np.max(np.abs(pitch_son))
51+
return pitch_son
52+
53+
54+
def sonify_pitch_activations_with_signal(P,
55+
x,
56+
frame_rate,
57+
Fs,
58+
min_pitch=1,
59+
Fc=440,
60+
harmonics_weights=(1,),
61+
fading_msec=5,
62+
stereo=True):
63+
"""Sonify a pitch activation matrix and combine it with a signal."""
64+
N = x.size
65+
pitch_son = sonify_pitch_activations(P, N, frame_rate, Fs, min_pitch=min_pitch, Fc=Fc,
66+
harmonics_weights=harmonics_weights, fading_msec=fading_msec)
67+
pitch_scaled = pitch_son * np.sqrt(np.mean(x ** 2)) / np.sqrt(np.mean(pitch_son ** 2))
68+
69+
if stereo:
70+
out = np.vstack((x, pitch_scaled))
71+
else:
72+
out = x + pitch_scaled
73+
74+
return pitch_son, out

0 commit comments

Comments
 (0)