Mencari Jarak Suara Menggunakan DTW#

Dynamic Time Warping (DTW)#

Dynamic Time Warping (DTW) merupakan teknik untuk mengukur tingkat kesamaan antara dua deret waktu yang kemungkinan memiliki durasi berbeda. Dalam konteks pengenalan suara, DTW berperan dalam mencocokkan pola akustik dari dua sinyal suara meskipun kecepatan atau panjang pengucapannya tidak sama.

DTW beroperasi dengan mencari lintasan penyesuaian (warping path) yang paling optimal di antara dua rangkaian fitur, contohnya fitur MFCC dari sampel audio dan audio input baru. Lintasan ini dipilih agar total jarak (biaya) antara kedua rangkaian menjadi minimum.

Inti Kerja DTW#

  1. Perhitungan Jarak Lokal - Menghitung jarak antara setiap pasangan elemen fitur dari dua sinyal

  2. Penyusunan Matriks Biaya Kumulatif - Membangun matriks menggunakan pemrograman dinamis

  3. Pencarian Jalur Optimal - Menemukan lintasan melalui matriks yang meminimalkan total biaya

  4. Pengambilan Nilai Akhir - Menggunakan nilai akhir sebagai jarak DTW

Semakin rendah nilai DTW yang diperoleh, semakin tinggi kemiripan antara kedua pola suara tersebut.

Suara Buka#

import os
import os
import librosa
import librosa.display
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import IPython.display as ipd

sns.set(style="whitegrid")
plt.rcParams['figure.figsize'] = (10, 4)
DATASET_PATH = "gabunganaudio" 
train_path = os.path.join(DATASET_PATH, "train")

Visualisasi Suara Waveforms#

def plot_all_tutup_timeseries(base_path, max_files=None):
    tutup_path = os.path.join(base_path, "buka")
    tutup_files = os.listdir(tutup_path)
    
    if max_files:
        tutup_files = tutup_files[:max_files]
    
    n_files = len(tutup_files)
    

    cols = 2
    rows = (n_files + cols - 1) // cols
    
    fig, axes = plt.subplots(rows, cols, figsize=(15, 3*rows))
    
    if rows == 1:
        axes = axes.reshape(1, -1)
    
    for i, filename in enumerate(tutup_files):
        file_path = os.path.join(tutup_path, filename)
        
        # Load audio
        y, sr = librosa.load(file_path, sr=None)
        
        # Hitung time axis
        time = np.linspace(0, len(y)/sr, len(y))
        
        # Plot
        row = i // cols
        col = i % cols
        
        axes[row, col].plot(time, y)
        axes[row, col].set_title(f'Buka - {filename}', fontsize=10)
        axes[row, col].set_xlabel('Waktu (detik)')
        axes[row, col].set_ylabel('Amplitude')
        axes[row, col].grid(True, alpha=0.3)
    
    for i in range(len(tutup_files), rows * cols):
        row = i // cols
        col = i % cols
        fig.delaxes(axes[row, col])
    
    plt.tight_layout()
    plt.show()
    
    print(f"Menampilkan {len(tutup_files)} file audio 'buka'")

plot_all_tutup_timeseries(train_path, max_files=10)
_images/bf6afe24dca28d09c169ca8de6765f8aa9aadda3e844ba35d671bee0a83f0fbe.png
Menampilkan 10 file audio 'buka'

Perhitungan Dynamic Time Warping#

import numpy as np
from dtaidistance import dtw
import pandas as pd
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import seaborn as sns

# def extract_features_for_dtw(audio_path, feature_type='mfcc'):
#     y, sr = librosa.load(audio_path, sr=22050)
#     mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)
#     mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
#     chroma = librosa.feature.chroma_stft(y=y, sr=sr)

#     mel_6_mean = np.mean(mel_spec_db[6, :])  
#     mel_6_std = np.std(mel_spec_db[6, :])    
#     mel_7_mean = np.mean(mel_spec_db[7, :])   
#     mel_7_std = np.std(mel_spec_db[7, :])    
#     chroma_0_mean = np.mean(chroma[0, :])    
    
#     from scipy.stats import skew
#     stat_skew = skew(y)
    
#     model_features = np.array([
#         mel_7_std,      
#         chroma_0_mean,
#         mel_6_std,      
#         stat_skew,      
#         mel_7_mean 
#     ])
    
#     return model_features

def extract_features_for_dtw(audio_path, n_mfcc=13):
    y, sr = librosa.load(audio_path, sr=22050)
    mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=n_mfcc)
    seq = mfcc[0, :]
    
    return seq



def calculate_dtw_distances(new_audio_path, train_path, max_files=100):
    try:
        # new_features = extract_features_for_dtw(new_audio_path, 'mfcc')
        new_features = extract_features_for_dtw(new_audio_path)
    except Exception as e:
        print(f"Errorrrrrrr")
        return None
    
    tutup_train_files = []
    train_labels = []
    
    tutup_path = os.path.join(train_path, "buka")
    if os.path.exists(tutup_path):
        for file in os.listdir(tutup_path):
            if file.endswith(('.wav')):
                tutup_train_files.append(os.path.join(tutup_path, file))
                train_labels.append('buka')
    
    if len(tutup_train_files) > max_files:
        indices = np.random.choice(len(tutup_train_files), max_files, replace=False)
        tutup_train_files = [tutup_train_files[i] for i in indices]
        train_labels = [train_labels[i] for i in indices]
    
    results = []
    
    for i, (train_file, label) in enumerate(zip(tutup_train_files, train_labels)):
        try:
            # Ekstrak fitur dari file training
            # train_features = extract_features_for_dtw(train_file, 'mfcc')
            train_features = extract_features_for_dtw(train_file)
            
            # Hitung jarak DTW
            dtw_distance = dtw.distance(new_features, train_features)
            
            results.append({
                'rank': i + 1,
                'filename': os.path.basename(train_file),
                'label': label,
                'dtw_distance': dtw_distance,
                'file_path': train_file
            })
            
                
        except Exception as e:
            print(f"  Error processing {train_file}: {e}")
            continue
    
    df_results = pd.DataFrame(results)
    df_results = df_results.sort_values('dtw_distance').reset_index(drop=True)
    df_results['rank'] = range(1, len(df_results) + 1)
    
    return df_results

# Path audio baru
new_audio_path = "gabunganaudio/train/bukanew.wav"

# Hitung jarak DTW
dtw_results = calculate_dtw_distances(new_audio_path, train_path, max_files=100)

if dtw_results is not None:
    print(f"\n=== RANKING JARAK DTW (Top 20) ===")
    print(dtw_results.head(20).to_string(index=False))
    
    print(f"\n=== STATISTIK HASIL ===")
    print(f"Total file dianalisis: {len(dtw_results)}")
    print(f"Jarak minimum: {dtw_results['dtw_distance'].min():.4f}")
    print(f"Jarak maksimum: {dtw_results['dtw_distance'].max():.4f}")
    print(f"Jarak rata-rata: {dtw_results['dtw_distance'].mean():.4f}")
    
=== RANKING JARAK DTW (Top 20) ===
 rank                 filename label  dtw_distance                                         file_path
    1 buka_11_aug_shift_23.wav  buka    382.517769 gabunganaudio/train/buka/buka_11_aug_shift_23.wav
    2  buka_11_aug_noise_3.wav  buka    392.582834  gabunganaudio/train/buka/buka_11_aug_noise_3.wav
    3              buka_11.wav  buka    447.051853              gabunganaudio/train/buka/buka_11.wav
    4  buka_2_aug_noise_28.wav  buka    466.331410  gabunganaudio/train/buka/buka_2_aug_noise_28.wav
    5               Buka37.wav  buka    483.874080               gabunganaudio/train/buka/Buka37.wav
    6              buka_18.wav  buka    499.659337              gabunganaudio/train/buka/buka_18.wav
    7 buka_18_aug_noise_14.wav  buka    499.782593 gabunganaudio/train/buka/buka_18_aug_noise_14.wav
    8              buka_12.wav  buka    510.688230              gabunganaudio/train/buka/buka_12.wav
    9              buka_19.wav  buka    519.059877              gabunganaudio/train/buka/buka_19.wav
   10               Buka11.wav  buka    522.181213               gabunganaudio/train/buka/Buka11.wav
   11              buka_16.wav  buka    525.251487              gabunganaudio/train/buka/buka_16.wav
   12 buka_15_aug_noise_24.wav  buka    535.851309 gabunganaudio/train/buka/buka_15_aug_noise_24.wav
   13   buka_5_aug_noise_9.wav  buka    541.601878   gabunganaudio/train/buka/buka_5_aug_noise_9.wav
   14              buka_13.wav  buka    545.225011              gabunganaudio/train/buka/buka_13.wav
   15              buka_17.wav  buka    548.849905              gabunganaudio/train/buka/buka_17.wav
   16              buka_15.wav  buka    549.044824              gabunganaudio/train/buka/buka_15.wav
   17  buka_19_aug_speed_7.wav  buka    565.323110  gabunganaudio/train/buka/buka_19_aug_speed_7.wav
   18               buka_2.wav  buka    570.292140               gabunganaudio/train/buka/buka_2.wav
   19               buka_1.wav  buka    571.017403               gabunganaudio/train/buka/buka_1.wav
   20               buka_7.wav  buka    571.789324               gabunganaudio/train/buka/buka_7.wav

=== STATISTIK HASIL ===
Total file dianalisis: 100
Jarak minimum: 382.5178
Jarak maksimum: 2314.7170
Jarak rata-rata: 949.0564

Perangkingan dan Perbandingan#

def visualize_dtw_results(dtw_results, new_audio_path):
    fig, ax = plt.subplots(1, 1, figsize=(15, 8))
    
    # Top 20 ranking
    top_20 = dtw_results.head(20)
    colors = ['red' if label == 'tutup' else 'blue' for label in top_20['label']]
    
    ax.barh(range(len(top_20)), top_20['dtw_distance'], color=colors, alpha=0.7)
    ax.set_yticks(range(len(top_20)))
    ax.set_yticklabels([f"{row['rank']}-{row['filename'][:15]}..." 
                        for _, row in top_20.iterrows()], fontsize=8)
    ax.set_xlabel('DTW Distance')
    ax.set_title('Top 20 Terdekat (Merah=Tutup, Biru=Buka)')
    ax.invert_yaxis()
    ax.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    # Waveform comparison
    print("\n=== PERBANDINGAN WAVEFORM ===")
    plot_waveform_comparison(new_audio_path, dtw_results.head(5))

def plot_waveform_comparison(new_audio_path, top_matches):
    fig, axes = plt.subplots(3, 2, figsize=(15, 12))
    
    # Audio baru
    y_new, sr_new = librosa.load(new_audio_path, sr=22050)
    time_new = np.linspace(0, len(y_new)/sr_new, len(y_new))
    
    axes[0, 0].plot(time_new, y_new, 'g-', linewidth=1)
    axes[0, 0].set_title(f'Audio Baru: {os.path.basename(new_audio_path)}')
    axes[0, 0].set_xlabel('Time (s)')
    axes[0, 0].set_ylabel('Amplitude')
    axes[0, 0].grid(True, alpha=0.3)
    
    for i, (_, row) in enumerate(top_matches.iterrows()):
        if i >= 5:
            break
            
        try:
            y_match, sr_match = librosa.load(row['file_path'], sr=22050)
            time_match = np.linspace(0, len(y_match)/sr_match, len(y_match))
            
            row_idx = (i + 1) // 2
            col_idx = (i + 1) % 2
            
            if row_idx < 3:
                color = 'red' if row['label'] == 'tutup' else 'blue'
                axes[row_idx, col_idx].plot(time_match, y_match, color=color, linewidth=1)
                axes[row_idx, col_idx].set_title(f'Rank {row["rank"]}: {row["filename"][:20]}...\nLabel: {row["label"]}, Distance: {row["dtw_distance"]:.4f}')
                axes[row_idx, col_idx].set_xlabel('Time (s)')
                axes[row_idx, col_idx].set_ylabel('Amplitude')
                axes[row_idx, col_idx].grid(True, alpha=0.3)
        except Exception as e:
            print(f"Error loading {row['filename']}: {e}")
            continue
    
    axes[0, 1].axis('off')
    
    plt.tight_layout()
    plt.show()

if dtw_results is not None:
    visualize_dtw_results(dtw_results, new_audio_path)
    
    print(f"\n=== SEMUA RANKING DTW (1-{len(dtw_results)}) ===")
    print(dtw_results[['rank', 'filename', 'label', 'dtw_distance']].to_string(index=False))
_images/9cd36c5cd0699c2e74787ccde666257e6201a52f5265ae3c12e822040ea31788.png
=== PERBANDINGAN WAVEFORM ===
_images/08b024557ad13d76c378102c7d59e6210b7a1e981ebd79c9f43b64dc0ed1f50a.png
=== SEMUA RANKING DTW (1-100) ===
 rank                 filename label  dtw_distance
    1 buka_11_aug_shift_23.wav  buka    382.517769
    2  buka_11_aug_noise_3.wav  buka    392.582834
    3              buka_11.wav  buka    447.051853
    4  buka_2_aug_noise_28.wav  buka    466.331410
    5               Buka37.wav  buka    483.874080
    6              buka_18.wav  buka    499.659337
    7 buka_18_aug_noise_14.wav  buka    499.782593
    8              buka_12.wav  buka    510.688230
    9              buka_19.wav  buka    519.059877
   10               Buka11.wav  buka    522.181213
   11              buka_16.wav  buka    525.251487
   12 buka_15_aug_noise_24.wav  buka    535.851309
   13   buka_5_aug_noise_9.wav  buka    541.601878
   14              buka_13.wav  buka    545.225011
   15              buka_17.wav  buka    548.849905
   16              buka_15.wav  buka    549.044824
   17  buka_19_aug_speed_7.wav  buka    565.323110
   18               buka_2.wav  buka    570.292140
   19               buka_1.wav  buka    571.017403
   20               buka_7.wav  buka    571.789324
   21 buka_17_aug_speed_30.wav  buka    580.075830
   22 buka_17_aug_speed_10.wav  buka    586.556769
   23 buka_12_aug_shift_11.wav  buka    586.733915
   24              buka_14.wav  buka    589.899303
   25               Buka10.wav  buka    615.295331
   26               buka_8.wav  buka    619.306442
   27   buka_4_aug_shift_1.wav  buka    622.727368
   28 buka_13_aug_pitch_25.wav  buka    622.958089
   29               buka_3.wav  buka    626.962319
   30   buka_2_aug_pitch_8.wav  buka    629.954066
   31 buka_19_aug_speed_27.wav  buka    631.396567
   32               Buka39.wav  buka    634.502044
   33               Buka38.wav  buka    639.834207
   34  buka_7_aug_pitch_15.wav  buka    640.230720
   35               Buka48.wav  buka    646.568539
   36  buka_14_aug_speed_6.wav  buka    650.918150
   37  buka_13_aug_speed_5.wav  buka    652.066091
   38 buka_16_aug_pitch_17.wav  buka    652.706193
   39               Buka46.wav  buka    664.676002
   40 buka_14_aug_speed_26.wav  buka    666.042135
   41               buka_6.wav  buka    667.596412
   42  buka_15_aug_speed_4.wav  buka    695.282047
   43               buka_5.wav  buka    706.296923
   44               buka_4.wav  buka    717.224316
   45  buka_8_aug_shift_13.wav  buka    735.082648
   46               Buka18.wav  buka    755.280784
   47  buka_5_aug_pitch_29.wav  buka    758.338274
   48                Buka3.wav  buka    784.932362
   49  buka_4_aug_pitch_21.wav  buka    789.061468
   50  buka_1_aug_shift_18.wav  buka    790.622342
   51  buka_3_aug_shift_19.wav  buka    795.142165
   52               buka_9.wav  buka    797.292528
   53  buka_9_aug_noise_16.wav  buka    797.325765
   54               Buka12.wav  buka    808.111340
   55               Buka28.wav  buka    826.630019
   56               Buka24.wav  buka    829.714409
   57                Buka5.wav  buka    851.459592
   58  buka_6_aug_speed_12.wav  buka    852.683595
   59               Buka20.wav  buka    865.154936
   60               Buka17.wav  buka    891.680261
   61               Buka47.wav  buka    933.133364
   62               Buka25.wav  buka    936.005609
   63               Buka29.wav  buka    951.142077
   64               Buka50.wav  buka    957.367778
   65               Buka40.wav  buka   1006.572650
   66  buka_20_aug_shift_2.wav  buka   1068.996609
   67              buka_20.wav  buka   1100.263264
   68                Buka2.wav  buka   1131.363116
   69               Buka13.wav  buka   1140.365950
   70                Buka8.wav  buka   1150.754318
   71               Buka23.wav  buka   1176.041613
   72               Buka45.wav  buka   1230.536925
   73               Buka35.wav  buka   1253.777293
   74               Buka14.wav  buka   1264.903455
   75               Buka31.wav  buka   1274.017661
   76                Buka6.wav  buka   1276.810381
   77                Buka1.wav  buka   1326.218779
   78           Buka1 copy.wav  buka   1326.218779
   79               Buka41.wav  buka   1341.496739
   80               Buka34.wav  buka   1377.812397
   81               Buka36.wav  buka   1401.549410
   82               Buka15.wav  buka   1416.006797
   83                Buka9.wav  buka   1441.992545
   84                Buka7.wav  buka   1509.431847
   85               Buka21.wav  buka   1519.238707
   86               Buka22.wav  buka   1519.238707
   87 buka_10_aug_speed_20.wav  buka   1538.938758
   88               Buka19.wav  buka   1548.983053
   89               Buka43.wav  buka   1550.959703
   90              buka_10.wav  buka   1558.685183
   91               Buka32.wav  buka   1562.079063
   92               Buka42.wav  buka   1593.204554
   93               Buka30.wav  buka   1634.844182
   94               Buka49.wav  buka   1641.325760
   95               Buka44.wav  buka   1727.224942
   96               Buka16.wav  buka   1733.980320
   97               Buka33.wav  buka   1847.588090
   98                Buka4.wav  buka   1897.660731
   99               Buka26.wav  buka   2201.897137
  100               Buka27.wav  buka   2314.717045

Suara Tutup#

import os
import os
import librosa
import librosa.display
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import IPython.display as ipd

sns.set(style="whitegrid")
plt.rcParams['figure.figsize'] = (10, 4)
DATASET_PATH = "gabunganaudio" 
train_path = os.path.join(DATASET_PATH, "train")

Visualisasi Suara Waveforms#

def plot_all_tutup_timeseries(base_path, max_files=None):
    tutup_path = os.path.join(base_path, "tutup")
    tutup_files = os.listdir(tutup_path)
    
    if max_files:
        tutup_files = tutup_files[:max_files]
    
    n_files = len(tutup_files)
    

    cols = 2
    rows = (n_files + cols - 1) // cols
    
    fig, axes = plt.subplots(rows, cols, figsize=(15, 3*rows))
    
    if rows == 1:
        axes = axes.reshape(1, -1)
    
    for i, filename in enumerate(tutup_files):
        file_path = os.path.join(tutup_path, filename)
        
        # Load audio
        y, sr = librosa.load(file_path, sr=None)
        
        # Hitung time axis
        time = np.linspace(0, len(y)/sr, len(y))
        
        # Plot
        row = i // cols
        col = i % cols
        
        axes[row, col].plot(time, y)
        axes[row, col].set_title(f'Tutup - {filename}', fontsize=10)
        axes[row, col].set_xlabel('Waktu (detik)')
        axes[row, col].set_ylabel('Amplitude')
        axes[row, col].grid(True, alpha=0.3)
    
    for i in range(len(tutup_files), rows * cols):
        row = i // cols
        col = i % cols
        fig.delaxes(axes[row, col])
    
    plt.tight_layout()
    plt.show()
    
    print(f"Menampilkan {len(tutup_files)} file audio 'tutup'")

plot_all_tutup_timeseries(train_path, max_files=10)
_images/2f9a3504685da194d9e8ca37e620cb4142376b0c304562d1b46d826af3f7beda.png
Menampilkan 10 file audio 'tutup'

Perhitungan Dynamic Time Warping#

import numpy as np
from dtaidistance import dtw
import pandas as pd
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import seaborn as sns

# def extract_features_for_dtw(audio_path, feature_type='mfcc'):
#     y, sr = librosa.load(audio_path, sr=22050)
#     mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)
#     mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
#     chroma = librosa.feature.chroma_stft(y=y, sr=sr)

#     mel_6_mean = np.mean(mel_spec_db[6, :])  
#     mel_6_std = np.std(mel_spec_db[6, :])    
#     mel_7_mean = np.mean(mel_spec_db[7, :])   
#     mel_7_std = np.std(mel_spec_db[7, :])    
#     chroma_0_mean = np.mean(chroma[0, :])    
    
#     from scipy.stats import skew
#     stat_skew = skew(y)
    
#     model_features = np.array([
#         mel_7_std,      
#         chroma_0_mean,
#         mel_6_std,      
#         stat_skew,      
#         mel_7_mean 
#     ])
    
#     return model_features

def extract_features_for_dtw(audio_path, n_mfcc=13):
    y, sr = librosa.load(audio_path, sr=22050)
    mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=n_mfcc)
    seq = mfcc[0, :]
    
    return seq



def calculate_dtw_distances(new_audio_path, train_path, max_files=100):
    try:
        # new_features = extract_features_for_dtw(new_audio_path, 'mfcc')
        new_features = extract_features_for_dtw(new_audio_path)
    except Exception as e:
        print(f"Errorrrrrrr")
        return None
    
    tutup_train_files = []
    train_labels = []
    
    tutup_path = os.path.join(train_path, "tutup")
    if os.path.exists(tutup_path):
        for file in os.listdir(tutup_path):
            if file.endswith(('.wav')):
                tutup_train_files.append(os.path.join(tutup_path, file))
                train_labels.append('tutup')
    
    if len(tutup_train_files) > max_files:
        indices = np.random.choice(len(tutup_train_files), max_files, replace=False)
        tutup_train_files = [tutup_train_files[i] for i in indices]
        train_labels = [train_labels[i] for i in indices]
    
    results = []
    
    for i, (train_file, label) in enumerate(zip(tutup_train_files, train_labels)):
        try:
            # Ekstrak fitur dari file training
            # train_features = extract_features_for_dtw(train_file, 'mfcc')
            train_features = extract_features_for_dtw(train_file)
            
            # Hitung jarak DTW
            dtw_distance = dtw.distance(new_features, train_features)
            
            results.append({
                'rank': i + 1,
                'filename': os.path.basename(train_file),
                'label': label,
                'dtw_distance': dtw_distance,
                'file_path': train_file
            })
            
                
        except Exception as e:
            print(f"  Error processing {train_file}: {e}")
            continue
    
    df_results = pd.DataFrame(results)
    df_results = df_results.sort_values('dtw_distance').reset_index(drop=True)
    df_results['rank'] = range(1, len(df_results) + 1)
    
    return df_results

# Path audio baru
new_audio_path = "gabunganaudio/train/tutupnew.wav"

# Hitung jarak DTW
dtw_results = calculate_dtw_distances(new_audio_path, train_path, max_files=100)

if dtw_results is not None:
    print(f"\n=== RANKING JARAK DTW (Top 20) ===")
    print(dtw_results.head(20).to_string(index=False))
    
    print(f"\n=== STATISTIK HASIL ===")
    print(f"Total file dianalisis: {len(dtw_results)}")
    print(f"Jarak minimum: {dtw_results['dtw_distance'].min():.4f}")
    print(f"Jarak maksimum: {dtw_results['dtw_distance'].max():.4f}")
    print(f"Jarak rata-rata: {dtw_results['dtw_distance'].mean():.4f}")
    
=== RANKING JARAK DTW (Top 20) ===
 rank                 filename label  dtw_distance                                          file_path
    1              Tutup20.wav tutup      0.000000              gabunganaudio/train/tutup/Tutup20.wav
    2 Tutup20_aug_shift_13.wav tutup     30.815539 gabunganaudio/train/tutup/Tutup20_aug_shift_13.wav
    3 Tutup20_aug_shift_10.wav tutup    106.067127 gabunganaudio/train/tutup/Tutup20_aug_shift_10.wav
    4              Tutup10.wav tutup    187.487968              gabunganaudio/train/tutup/Tutup10.wav
    5  Tutup7_aug_noise_29.wav tutup    192.054720  gabunganaudio/train/tutup/Tutup7_aug_noise_29.wav
    6               Tutup6.wav tutup    192.945325               gabunganaudio/train/tutup/Tutup6.wav
    7  Tutup9_aug_noise_26.wav tutup    219.962346  gabunganaudio/train/tutup/Tutup9_aug_noise_26.wav
    8              Tutup19.wav tutup    232.378244              gabunganaudio/train/tutup/Tutup19.wav
    9              Tutup16.wav tutup    234.219749              gabunganaudio/train/tutup/Tutup16.wav
   10 Tutup18_aug_speed_11.wav tutup    236.572515 gabunganaudio/train/tutup/Tutup18_aug_speed_11.wav
   11              Tutup14.wav tutup    238.276576              gabunganaudio/train/tutup/Tutup14.wav
   12  Tutup18_aug_pitch_1.wav tutup    244.708348  gabunganaudio/train/tutup/Tutup18_aug_pitch_1.wav
   13              Tutup15.wav tutup    251.552345              gabunganaudio/train/tutup/Tutup15.wav
   14               Tutup3.wav tutup    258.211003               gabunganaudio/train/tutup/Tutup3.wav
   15 Tutup19_aug_speed_22.wav tutup    261.896002 gabunganaudio/train/tutup/Tutup19_aug_speed_22.wav
   16               Tutup7.wav tutup    271.630839               gabunganaudio/train/tutup/Tutup7.wav
   17               Tutup5.wav tutup    275.792622               gabunganaudio/train/tutup/Tutup5.wav
   18  Tutup19_aug_pitch_4.wav tutup    283.493676  gabunganaudio/train/tutup/Tutup19_aug_pitch_4.wav
   19               Tutup8.wav tutup    293.069411               gabunganaudio/train/tutup/Tutup8.wav
   20  Tutup8_aug_noise_23.wav tutup    306.586397  gabunganaudio/train/tutup/Tutup8_aug_noise_23.wav

=== STATISTIK HASIL ===
Total file dianalisis: 50
Jarak minimum: 0.0000
Jarak maksimum: 1123.6811
Jarak rata-rata: 379.2039

Perangkingan dan Perbandingan#

def visualize_dtw_results(dtw_results, new_audio_path):
    fig, ax = plt.subplots(1, 1, figsize=(15, 8))
    
    # Top 20 ranking
    top_20 = dtw_results.head(20)
    colors = ['red' if label == 'tutup' else 'blue' for label in top_20['label']]
    
    ax.barh(range(len(top_20)), top_20['dtw_distance'], color=colors, alpha=0.7)
    ax.set_yticks(range(len(top_20)))
    ax.set_yticklabels([f"{row['rank']}-{row['filename'][:15]}..." 
                        for _, row in top_20.iterrows()], fontsize=8)
    ax.set_xlabel('DTW Distance')
    ax.set_title('Top 20 Terdekat (Merah=Tutup, Biru=Buka)')
    ax.invert_yaxis()
    ax.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    print("\n=== PERBANDINGAN WAVEFORM ===")
    plot_waveform_comparison(new_audio_path, dtw_results.head(5))

def plot_waveform_comparison(new_audio_path, top_matches):
    fig, axes = plt.subplots(3, 2, figsize=(15, 12))
    
    # Audio baru
    y_new, sr_new = librosa.load(new_audio_path, sr=22050)
    time_new = np.linspace(0, len(y_new)/sr_new, len(y_new))
    
    axes[0, 0].plot(time_new, y_new, 'g-', linewidth=1)
    axes[0, 0].set_title(f'Audio Baru: {os.path.basename(new_audio_path)}')
    axes[0, 0].set_xlabel('Time (s)')
    axes[0, 0].set_ylabel('Amplitude')
    axes[0, 0].grid(True, alpha=0.3)
    
    for i, (_, row) in enumerate(top_matches.iterrows()):
        if i >= 5:
            break
            
        try:
            y_match, sr_match = librosa.load(row['file_path'], sr=22050)
            time_match = np.linspace(0, len(y_match)/sr_match, len(y_match))
            
            row_idx = (i + 1) // 2
            col_idx = (i + 1) % 2
            
            if row_idx < 3:
                color = 'red' if row['label'] == 'tutup' else 'blue'
                axes[row_idx, col_idx].plot(time_match, y_match, color=color, linewidth=1)
                axes[row_idx, col_idx].set_title(f'Rank {row["rank"]}: {row["filename"][:20]}...\nLabel: {row["label"]}, Distance: {row["dtw_distance"]:.4f}')
                axes[row_idx, col_idx].set_xlabel('Time (s)')
                axes[row_idx, col_idx].set_ylabel('Amplitude')
                axes[row_idx, col_idx].grid(True, alpha=0.3)
        except Exception as e:
            print(f"Error loading {row['filename']}: {e}")
            continue
    
    axes[0, 1].axis('off')
    
    plt.tight_layout()
    plt.show()

if dtw_results is not None:
    visualize_dtw_results(dtw_results, new_audio_path)
    
    print(f"\n=== SEMUA RANKING DTW (1-{len(dtw_results)}) ===")
    print(dtw_results[['rank', 'filename', 'label', 'dtw_distance']].to_string(index=False))
_images/2d924f267ea2b66275db11fad39a829395387cd53f8a6d489a1518fbbbc33eeb.png
=== PERBANDINGAN WAVEFORM ===
_images/4ff7f47a09f942143cc250caa5f6e7132451a909c11326201602e4ed46427c7b.png
=== SEMUA RANKING DTW (1-50) ===
 rank                 filename label  dtw_distance
    1              Tutup20.wav tutup      0.000000
    2 Tutup20_aug_shift_13.wav tutup     30.815539
    3 Tutup20_aug_shift_10.wav tutup    106.067127
    4              Tutup10.wav tutup    187.487968
    5  Tutup7_aug_noise_29.wav tutup    192.054720
    6               Tutup6.wav tutup    192.945325
    7  Tutup9_aug_noise_26.wav tutup    219.962346
    8              Tutup19.wav tutup    232.378244
    9              Tutup16.wav tutup    234.219749
   10 Tutup18_aug_speed_11.wav tutup    236.572515
   11              Tutup14.wav tutup    238.276576
   12  Tutup18_aug_pitch_1.wav tutup    244.708348
   13              Tutup15.wav tutup    251.552345
   14               Tutup3.wav tutup    258.211003
   15 Tutup19_aug_speed_22.wav tutup    261.896002
   16               Tutup7.wav tutup    271.630839
   17               Tutup5.wav tutup    275.792622
   18  Tutup19_aug_pitch_4.wav tutup    283.493676
   19               Tutup8.wav tutup    293.069411
   20  Tutup8_aug_noise_23.wav tutup    306.586397
   21               Tutup4.wav tutup    311.507674
   22  Tutup8_aug_shift_30.wav tutup    312.747102
   23   Tutup9_aug_noise_3.wav tutup    320.994061
   24               Tutup9.wav tutup    322.041195
   25              Tutup13.wav tutup    325.722106
   26   Tutup4_aug_shift_6.wav tutup    330.671098
   27  Tutup11_aug_shift_2.wav tutup    337.190101
   28 Tutup10_aug_pitch_15.wav tutup    347.444139
   29  Tutup17_aug_pitch_9.wav tutup    351.177295
   30              Tutup11.wav tutup    365.077903
   31              Tutup18.wav tutup    368.382189
   32   Tutup5_aug_speed_7.wav tutup    371.252146
   33 Tutup13_aug_speed_27.wav tutup    382.481209
   34 Tutup16_aug_pitch_21.wav tutup    440.627500
   35  Tutup5_aug_pitch_18.wav tutup    442.417012
   36 Tutup15_aug_speed_16.wav tutup    442.877805
   37               Tutup1.wav tutup    445.813372
   38 Tutup14_aug_speed_17.wav tutup    455.619479
   39              Tutup17.wav tutup    474.272733
   40  Tutup2_aug_shift_20.wav tutup    484.671957
   41 Tutup15_aug_speed_19.wav tutup    501.015594
   42               Tutup2.wav tutup    512.588529
   43  Tutup12_aug_pitch_8.wav tutup    533.748156
   44              Tutup12.wav tutup    535.914172
   45  Tutup17_aug_shift_5.wav tutup    554.431776
   46  Tutup2_aug_speed_14.wav tutup    588.016077
   47  Tutup1_aug_noise_28.wav tutup    638.567122
   48  Tutup4_aug_noise_25.wav tutup    927.430186
   49 Tutup11_aug_noise_12.wav tutup   1094.094089
   50 Tutup12_aug_noise_24.wav tutup   1123.681060