|
| 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