본문 바로가기

이직로그/DSP

Audio DSP: 일렉기타 이펙터 만들기 3 - 다양한 데이터 셋으로 전달함수 만들기

이전에 실제 기타 사운드로 전달함수를 만들었더니 다른 input에 적용했을 때 제대로 적용되지 않는 소리가 들렸다.

그 이유는 2가지 중 하나일것이다.

1. 전달함수를 만들기에는 적절하지 않은 인풋값이었다.

2. 오버드라이브는 LTI 시스템이 아니다

 

사실 2번이 맞다는거는 진행하면서 알고있었다.

오버드라이브 자체가 compress 효과를 주기 때문에 비선형 시스템일 가능성이 농후했기 때문이다.

 

하지만 그래도 다른 인풋값을 적용해보면 최소한 EQ 정도는 비슷하게 나오지 않을까 싶었다.

 

그래서 4가지 인풋 데이터를 준비해봤다.

 

1. 440Hz sine 파

2. impulse response

3. gaussian noise

4. clean guitar 

 

그리고

- 전달함수 근사

- 전달함수 적용

- main

 세가지 파일로 코드를 분리했다.

 

아래 전달함수 근사 코드 보기

더보기
더보기

 

function [H, f] = estimate_transfer_function(x, y, fs, doPlot)

    if nargin < 4
        doPlot = true;
    end

    if size(x,2) > 1, x = mean(x,2); end
    if size(y,2) > 1, y = mean(y,2); end
    x = x(:);
    y = y(:);

    N = min(length(x), length(y));
    x = x(1:N);
    y = y(1:N);

    [c, lags] = xcorr(y, x);
    [~, idx] = max(abs(c));
    delay = lags(idx);

    if delay > 0
        y = y(delay+1:end);
        x = x(1:end-delay);
    elseif delay < 0
        x = x(-delay+1:end);
        y = y(1:end+delay);
    end

    N = min(length(x), length(y));
    x = x(1:N);
    y = y(1:N);

    w = hann(N);
    X = fft(x .* w);
    Y = fft(y .* w);

    lambda = 1e-6 * max(abs(X).^2);
    H = (Y .* conj(X)) ./ (abs(X).^2 + lambda);

    % magnitude만 smoothing
    Hmag = smoothdata(abs(H), 'movmean', 31);
    Hpha = angle(H);
    H = Hmag .* exp(1j * Hpha);

    % gain 제한
    maxGain = 8;
    H = min(abs(H), maxGain) .* exp(1j * angle(H));

    f = (0:N-1)' * (fs/N);

    if doPlot
        halfIdx = 1:floor(N/2);

        figure;
        subplot(2,1,1);
        semilogx(f(halfIdx), 20*log10(abs(H(halfIdx)) + 1e-12), 'LineWidth', 1.2);
        xlabel('Frequency (Hz)');
        ylabel('Magnitude (dB)');
        title('Estimated Transfer Function Magnitude');
        xlim([20 fs/2]);
        grid on;

        subplot(2,1,2);
        semilogx(f(halfIdx), unwrap(angle(H(halfIdx))), 'LineWidth', 1.2);
        xlabel('Frequency (Hz)');
        ylabel('Phase (rad)');
        title('Estimated Transfer Function Phase');
        xlim([20 fs/2]);
        grid on;
    end
end

 

 

아래 전달함수 적용 코드 보기

더보기
더보기
function y_out = apply_transfer_function(x_in, H)
% apply_transfer_function
% x_in : 새 입력 신호
% H    : estimate_transfer_function에서 추정한 전달함수

    if size(x_in,2) > 1
        x_in = mean(x_in,2);
    end
    x_in = x_in(:);

    N_in = length(x_in);
    N_H  = length(H);

    % H 길이에 맞춰 보간
    old_idx = linspace(0, 1, N_H);
    new_idx = linspace(0, 1, N_in);

    H_real = interp1(old_idx, real(H), new_idx, 'linear', 0);
    H_imag = interp1(old_idx, imag(H), new_idx, 'linear', 0);
    H_new = H_real(:) + 1j * H_imag(:);

    % 적용
    X_in = fft(x_in, N_in);
    Y_out = X_in .* H_new;
    y_out = real(ifft(Y_out));

    % normalize
    peak = max(abs(y_out));
    if peak > 0
        y_out = 0.95 * y_out / peak;
    end
end

main 코드 보기

더보기
더보기
IN = {'DATA/sine_440Hz.wav' 
    'DATA/impulse_response.wav'
    'DATA/gaussian_noise.wav'
    'DATA/clean1.wav'};

OUT = {'DATA/f(sine_440Hz).wav' 
    'DATA/f(impulse_response).wav' 
    'DATA/f(gaussian_noise).wav'
    'DATA/janray.wav'
    };

output_file_name = {
    'OUTPUT/s_estimated_output.wav'
    'OUTPUT/i_estimated_output.wav'
    'OUTPUT/g_estimated_output.wav'
    'OUTPUT/c_estimated_output.wav'
};
%% num 값으로 어떤 데이터를 이용하는 지 적용
num = 1;
%%
% 파일 경로
in  = IN{num};
out = OUT{num};
test_in      = 'DATA/clean2.wav';

% 읽기
[x, fs] = audioread(in);
[y, fs2] = audioread(out);
[x_test, fs3] = audioread(test_in);

if fs ~= fs2 || fs ~= fs3
    error('샘플링 레이트가 다릅니다.');
end

% 전달함수 추정
[H, f] = estimate_transfer_function(x, y, fs, true);

% 새 입력에 적용
y_est = apply_transfer_function(x_test, H);

% 저장
audiowrite(output_file_name{num}, y_est, fs);

% 확인용 파형
figure;
subplot(2,1,1);
plot(x_test);
title('Input Test Signal');

subplot(2,1,2);
plot(y_est);
title('Estimated Output Signal');

 

 

 

좌: sin 전달함수 우: impulse 전달함수
좌: gaussian 전달함수 우: clean 기타 전달함수

 

sin과 impulse는 전달함수가 유의미 해보이지는 않는다.

하지만 gaussian 과 clean 기타의 경우에는 비슷한 양상을 보이는게 좀더 유의미한 양상을 보이는고

clean보다 gaussian에서 좀더 조밀한 주파수 대역을 가지지 않나 생각이 든다.

 

gaussian에서 더 다양한 주파수 대역을 가지기 때문이라고생각이 들었다

 

그리고 output들의 진폭 그래프와 실제 소리를 들어보자

 

Time Domain 진폭 그래프

더보기
더보기
input

 

sine

 

impulse

 

gaussian

 

clean



 

clean gaussian impulse sin 순으로 소리가 잘 들리는 것을 알 수 있다.

그리고 결과적으로 오버드라이브는 LTI 시스템이 아니기 때문에 전달함수를 이용해서 시뮬레이션 하는것을 바람직 하지 않음을 알수있었다.

 

 

 

결론:

sine, impulse, gaussian noise를 이용해 전달함수를 추정한 결과가 일관되지 않았고, 이를 다른 입력에 적용했을 때 리버브성 왜곡이 발생하였다. 따라서 오버드라이브를 단일 LTI 전달함수로 모델링하는 방식은 적절하지 않다고 판단하였다.

본 프로젝트를 통해 오버드라이브의 핵심은 선형 주파수 응답보다 비선형 고조파 생성에 있음을 확인하였다. 따라서 향후에는 전달함수 기반 접근보다 soft clipping, waveshaping, 또는 비선형 블랙박스 모델 기반 접근이 더 적절할 것으로 판단된다.