Created
March 6, 2026 02:01
-
-
Save ravarcheon/28d48aac767930cecd3dc536f1576581 to your computer and use it in GitHub Desktop.
makes a minimum phase version of the input audio
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import numpy as np | |
| import soundfile as sf | |
| import os | |
| import tkinter as tk | |
| from tkinter import filedialog | |
| from scipy.fft import fft, ifft, next_fast_len | |
| def minipassMethod(ir): | |
| ir = np.asarray(ir, dtype=np.float64) | |
| N = len(ir) | |
| L = next_fast_len(16 * N) # zero pad to efficient length, anything above 2*N pretty much. the bigger, the less artifacts you get | |
| # should probably actually find a better purer way of doing this, im unsure if its possible to actually get a perfect minimized phase at all but im hoping somewhere out there in the vast realm of signal processing that there is some sure-fire way of actually minning the phase completely without any artifacts or ringing or reflections or whateverhaveyou within a reasonable time complexity and memory complexity hopefully at most nlogn on both (math crack pipe dream honestly) | |
| # god i really don't know what im doing | |
| print("fft") | |
| spectrum = fft(ir, n=L) # fft of padded signal | |
| mag = np.abs(spectrum) | |
| mag = np.maximum(mag, np.finfo(np.float64).tiny) | |
| print("cep") | |
| cepstrum = ifft(np.log(mag)).real # i think its important to remember that numpy does the natural log. i keep forgetting this over time lmaooaoa | |
| print("min") | |
| minphaseCep = np.zeros_like(cepstrum) | |
| minphaseCep[0] = cepstrum[0] | |
| # this odd even check will pretty much always be even unless you decide change that at the definition of L | |
| if L % 2 == 0: | |
| minphaseCep[1:L // 2] = 2 * cepstrum[1:L // 2] | |
| minphaseCep[L // 2] = cepstrum[L // 2] # no nyquist change | |
| else: | |
| minphaseCep[1:(L + 1) // 2] = 2 * cepstrum[1:(L + 1) // 2] | |
| print("recon") | |
| minphaseSpectrum = np.exp(fft(minphaseCep)) | |
| minphaseIR = ifft(minphaseSpectrum).real[:N] | |
| return minphaseIR | |
| def processAudio(input, out): | |
| data, sr = sf.read(input, always_2d=True) | |
| minphaseSignal = np.stack([minipassMethod(ch) for ch in data.T], axis=1) | |
| sf.write(out, minphaseSignal, sr, subtype='FLOAT') | |
| if __name__ == "__main__": | |
| root = tk.Tk() | |
| root.withdraw() | |
| filePath = filedialog.askopenfilename(filetypes=[("Audio files", "*.wav *.flac *.aiff *.ogg *.mp3 *.m4a *.opus")]) | |
| if filePath: | |
| dirName, baseName = os.path.split(filePath) | |
| baseName = os.path.splitext(baseName)[0] | |
| outName = f"minphase {baseName}.wav" | |
| outPath = os.path.join(dirName, outName) | |
| processAudio(filePath, outPath) | |
| print(f"minphase@: {outPath}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment