| import parselmouth |
| from parselmouth.praat import call |
| import numpy as np |
| from scipy.stats import entropy |
|
|
| def extract_voice_features(audio_file): |
|
|
| sound = parselmouth.Sound(audio_file) |
| pitch = call(sound, "To Pitch", 0.0, 75, 600) |
|
|
| |
| pointprocess = call(sound, "To PointProcess (periodic, cc)", 75, 600) |
| |
| |
| jitter_percent = call(pointprocess, "Get jitter (local)", 0, 0, 0.0001, 0.02, 1.3) |
| jitter_abs = call(pointprocess, "Get jitter (local, absolute)", 0, 0, 0.0001, 0.02, 1.3) |
| jitter_rap = call(pointprocess, "Get jitter (rap)", 0, 0, 0.0001, 0.02, 1.3) |
| jitter_ppq5 = call(pointprocess, "Get jitter (ppq5)", 0, 0, 0.0001, 0.02, 1.3) |
| jitter_ddp = call(pointprocess, "Get jitter (ddp)", 0, 0, 0.0001, 0.02, 1.3) |
| |
| |
| shimmer = call([sound, pointprocess], "Get shimmer (local)", 0, 0, 0.0001, 0.02, 1.3, 1.6) |
| shimmer_db = call([sound, pointprocess], "Get shimmer (local_dB)", 0, 0, 0.0001, 0.02, 1.3, 1.6) |
| shimmer_apq3 = call([sound, pointprocess], "Get shimmer (apq3)", 0, 0, 0.0001, 0.02, 1.3, 1.6) |
| shimmer_apq5 = call([sound, pointprocess], "Get shimmer (apq5)", 0, 0, 0.0001, 0.02, 1.3, 1.6) |
| shimmer_apq11 = call([sound, pointprocess], "Get shimmer (apq11)", 0, 0, 0.0001, 0.02, 1.3, 1.6) |
| shimmer_dda = call([sound, pointprocess], "Get shimmer (dda)", 0, 0, 0.0001, 0.02, 1.3, 1.6) |
| |
| |
| harmonicity = call(sound, "To Harmonicity (cc)", 0.01, 75, 0.1, 1.0) |
| hnr = call(harmonicity, "Get mean", 0, 0) |
| |
| |
| |
| |
| if hnr > 0: |
| nhr_value = 1.0 / (10 ** (hnr / 10)) |
| else: |
| nhr_value = float('inf') |
| |
| |
| |
| pitch_values = pitch.selected_array['frequency'] |
| voiced_frames = pitch_values[pitch_values > 0] |
| |
| if len(voiced_frames) > 0: |
| |
| periods = 1.0 / voiced_frames |
| else: |
| periods = np.array([]) |
| |
| |
| if len(periods) < 50: |
| rpde = dfa = ppe = np.nan |
| else: |
| |
| hist, _ = np.histogram(periods, bins=min(20, len(periods)//5), density=True) |
| hist = hist[hist > 0] |
| ppe = entropy(hist + 1e-10) |
| |
| |
| diffs = np.abs(np.diff(periods)) |
| rec_threshold = np.std(diffs) * 0.1 |
| rec_matrix = (np.abs(periods[:, None] - periods[None, :]) < rec_threshold).astype(float) |
| np.fill_diagonal(rec_matrix, 0) |
| hist_rpde, _ = np.histogram(rec_matrix.flatten(), bins=2, density=True) |
| rpde = entropy(hist_rpde + 1e-10) |
| |
| |
| y = np.cumsum(periods - np.mean(periods)) |
| scales = np.logspace(np.log10(4), np.log10(len(y)/4), 8, dtype=int) |
| log_F = [] |
| log_s = [] |
| for s in scales: |
| if s > len(y) // 4: |
| continue |
| num_seg = len(y) // s |
| rms = [] |
| for i in range(num_seg): |
| seg = y[i*s:(i+1)*s] |
| if len(seg) < 3: |
| continue |
| x = np.arange(len(seg)) |
| p = np.polyfit(x, seg, 1) |
| detrend = seg - np.polyval(p, x) |
| rms.append(np.sqrt(np.mean(detrend**2))) |
| if rms: |
| F = np.sqrt(np.mean(rms)) |
| log_F.append(np.log(F)) |
| log_s.append(np.log(s)) |
| if len(log_F) > 1: |
| slope, _ = np.polyfit(log_s, log_F, 1) |
| dfa = slope |
| else: |
| dfa = np.nan |
| |
| return { |
| 'Jitter(%)': jitter_percent * 100, |
| 'Jitter(Abs)': jitter_abs, |
| 'Jitter:RAP': jitter_rap, |
| 'Jitter:PPQ5': jitter_ppq5, |
| 'Jitter:DDP': jitter_ddp, |
| 'Shimmer': shimmer, |
| 'Shimmer(dB)': shimmer_db, |
| 'Shimmer:APQ3': shimmer_apq3, |
| 'Shimmer:APQ5': shimmer_apq5, |
| 'Shimmer:APQ11': shimmer_apq11, |
| 'Shimmer:DDA': shimmer_dda, |
| 'NHR': nhr_value, |
| 'HNR': hnr, |
| 'RPDE': float(rpde), |
| 'DFA': float(dfa), |
| 'PPE': float(ppe) |
| } |
|
|
|
|
| |
| |
| |
| |