function MKS_04b % MKS_04b - SPEKTRÁLNÍ ZPRACOVÁNÍ ZVUKU (B2M31SYN, 2026) % Spektrální filtrace / robot / šepot / změna délky / transpozice / potlačení šumu. Bez dalších toolboxů. % Soubory why11.wav, Tom.wav a trumpet.wav uložte do stejné složky jako tento skript. % Spuštění v MATLABu: MKS_04b % % Výpočet STFT/ISTFT s normalizací váženého překrývání a sčítání rámců (WOLA). % Spektrální úpravy zachovávají komplexně sdruženou symetrii (reálný výstup). root = fileparts(mfilename('fullpath')); S.fs = 22050; S.clean = []; S.x = []; S.y = []; S.file = ''; S.addedNoise = []; S.toneFrequency = 430; S.toneSNR = 0; S.effect = 'STRETCH'; S.player = []; S.winLength = 1024; S.hop = S.winLength/4; fig = figure('Name','MKS 04b - SPECTRAL AUDIO PROCESSOR', ... 'NumberTitle','off','MenuBar','none','ToolBar','none', ... 'Color',[.95 .96 .97],'Position',[140 80 1080 730], ... 'Resize','off','CloseRequestFcn',@closeFigure); uicontrol(fig,'Style','text','String','MKS 04b - SPECTRAL AUDIO PROCESSOR', ... 'Position',[110 687 860 30],'FontSize',18,'FontWeight','bold', ... 'BackgroundColor',get(fig,'Color')); % Výběr vstupního signálu uicontrol(fig,'Style','text','String','SOURCE', ... 'Position',[45 642 90 24],'FontSize',11,'FontWeight','bold', ... 'BackgroundColor',get(fig,'Color')); source = uicontrol(fig,'Style','popupmenu', ... 'String',{'why11.wav','Tom.wav','trumpet.wav','LOAD WAV ...'}, ... 'Position',[145 642 235 27],'FontSize',11,'Callback',@sourceChanged); fileText = uicontrol(fig,'Style','text','String','', ... 'Position',[400 642 630 25],'HorizontalAlignment','left', ... 'BackgroundColor',get(fig,'Color')); % Volitelné přičtení sinusového rušení (SNR vzhledem k čistému signálu) noiseCheck = uicontrol(fig,'Style','checkbox','String','ADD SINUSOID', ... 'Position',[45 605 155 25],'Value',0,'FontWeight','bold', ... 'BackgroundColor',get(fig,'Color'),'Callback',@interferenceChanged); uicontrol(fig,'Style','text','String','f (Hz)', ... 'Position',[215 607 55 20],'BackgroundColor',get(fig,'Color')); toneFreqEdit = uicontrol(fig,'Style','edit','String','430', ... 'Position',[274 605 78 26],'Callback',@interferenceChanged); uicontrol(fig,'Style','text','String','SNR', ... 'Position',[382 607 42 20],'BackgroundColor',get(fig,'Color')); snrSlider = uicontrol(fig,'Style','slider','Min',-10,'Max',30,'Value',0, ... 'Position',[430 608 260 20],'Callback',@interferenceChanged); snrText = uicontrol(fig,'Style','text','String','0 dB', ... 'Position',[704 607 70 20],'BackgroundColor',get(fig,'Color')); whiteNoiseCheck = uicontrol(fig,'Style','checkbox','String','ADD WHITE NOISE', ... 'Position',[788 605 230 25],'Value',0,'FontWeight','bold', ... 'BackgroundColor',get(fig,'Color'),'Callback',@interferenceChanged); % Výběr efektu panel = uibuttongroup(fig,'Title','PROCESSOR / EFFECT','FontWeight','bold', ... 'Units','pixels','Position',[49 507 983 82], ... 'SelectionChangedFcn',@effectChanged); names = {'STRETCH','PITCH','ROBOT','WHISPER','BLUR','FILTER','DENOISE'}; for k = 1:numel(names) uicontrol(panel,'Style','radiobutton','String',names{k},'Tag',names{k}, ... 'Position',[18+(k-1)*137 20 132 28],'FontSize',12,'Value',strcmp(names{k},'STRETCH')); end % Tři ovládací prvky efektu paramTitle = uicontrol(fig,'Style','text','String','STRETCH: phase vocoder, duration changes without pitch shift', ... 'Position',[70 482 940 24],'FontSize',11,'FontWeight','bold', ... 'BackgroundColor',get(fig,'Color')); labels = {'CENTER FREQUENCY','NOTCH WIDTH','DRY / WET'}; positions = [105 415 725]; sl = gobjects(1,3); valText = gobjects(1,3); nameText = gobjects(1,3); for k=1:3 nameText(k) = uicontrol(fig,'Style','text','String',labels{k}, ... 'Position',[positions(k) 443 250 23],'FontSize',10, ... 'BackgroundColor',get(fig,'Color')); sl(k) = uicontrol(fig,'Style','slider','Min',0,'Max',1,'Value',.5, ... 'Position',[positions(k) 414 250 22],'Callback',@sliderChanged); valText(k) = uicontrol(fig,'Style','text','String','', ... 'Position',[positions(k) 391 250 20],'FontSize',10, ... 'BackgroundColor',get(fig,'Color')); end % Nastavení délky okna pro časové, výškové a fázové efekty. FILTER a DENOISE % záměrně používají N=1024; všechny efekty pracují s překryvem 75 %. windowChoices=[256 512 1024 2048 4096]; windowLabel=uicontrol(fig,'Style','text','String','WINDOW LENGTH', ... 'Position',[92 369 130 19],'FontSize',9,'BackgroundColor',get(fig,'Color')); windowSlider=uicontrol(fig,'Style','slider','Min',1,'Max',5,'Value',3, ... 'SliderStep',[.25 .25],'Position',[222 371 190 17], ... 'Callback',@windowChanged); windowInfo=uicontrol(fig,'Style','text','String','', ... 'Position',[420 369 600 20],'HorizontalAlignment','left', ... 'FontSize',9,'BackgroundColor',get(fig,'Color')); % Osy spektrogramů ax1 = axes('Parent',fig,'Units','pixels','Position',[78 167 438 197]); ax2 = axes('Parent',fig,'Units','pixels','Position',[595 167 438 197]); % Ovládání přehrávání a ukládání uicontrol(fig,'Style','pushbutton','String','PLAY ORIGINAL', ... 'Position',[65 92 175 42],'FontSize',11,'Callback',@playOriginal); uicontrol(fig,'Style','pushbutton','String','PROCESS', ... 'Position',[259 92 175 42],'FontSize',11,'FontWeight','bold', ... 'Callback',@processButton); uicontrol(fig,'Style','pushbutton','String','PLAY RESULT', ... 'Position',[453 92 175 42],'FontSize',11,'Callback',@playResult); uicontrol(fig,'Style','pushbutton','String','STOP', ... 'Position',[647 92 150 42],'FontSize',11,'Callback',@stopAudio); uicontrol(fig,'Style','pushbutton','String','SAVE WAV', ... 'Position',[816 92 175 42],'FontSize',11,'Callback',@saveWav); status = uicontrol(fig,'Style','text','String','', ... 'Position',[60 40 960 29],'FontSize',10, ... 'HorizontalAlignment','center','BackgroundColor',get(fig,'Color')); % Výchozí stav: obě rušení vypnutá, STRETCH 1,00×, plně zpracovaný signál. % Rozsahy: STRETCH R=0,2 až 5; PITCH −24 až +24 půltónů; upravené pořadí efektů. set(sl(1),'Value',(1-0.2)/(5-0.2)); set(sl(2),'Value',.5); set(sl(3),'Value',1); refreshControls(); loadAudio(fullfile(root,'why11.wav')); function sourceChanged(~,~) n = get(source,'Value'); if n<4 loadAudio(fullfile(root,namesSource(n))); else [f,p]=uigetfile({'*.wav','WAV audio (*.wav)'},'Select audio file',root); if isequal(f,0), return; end loadAudio(fullfile(p,f)); end end function name = namesSource(k) files = {'why11.wav','Tom.wav','trumpet.wav'}; name = files{k}; end function loadAudio(path) if ~exist(path,'file') set(status,'String',['Missing WAV file: ' path]); return; end try [a,fs]=audioread(path); a=mean(a,2); % převod sterea na mono a=a-mean(a); S.clean=a; S.fs=fs; S.file=path; S.y=[]; if S.toneFrequency>=fs/2 S.toneFrequency=min(430,fs/4); set(toneFreqEdit,'String',num2str(S.toneFrequency)); end updateInput(); refreshWindowInfo(); [~,f,e]=fileparts(path); set(fileText,'String',sprintf('%s%s | %.2f s | %d Hz',f,e,numel(a)/fs,fs)); showSpectrogram(ax1,S.x,S.fs,'INPUT'); cla(ax2); title(ax2,'PROCESSED (press PROCESS)'); set(status,'String','Loaded. Choose effect, adjust sliders and press PROCESS.'); catch ME set(status,'String',['Load error: ' ME.message]); end end function interferenceChanged(~,~) f = str2double(get(toneFreqEdit,'String')); if ~isfinite(f) || f<=0 || f>=S.fs/2 set(toneFreqEdit,'String',num2str(S.toneFrequency)); set(status,'String',sprintf('Sinusoid frequency must be between 0 and %.0f Hz.',S.fs/2)); return; end S.toneFrequency=f; S.toneSNR=get(snrSlider,'Value'); updateInput(); end function updateInput() if isempty(S.clean), return; end S.x=S.clean; S.y=[]; S.addedNoise=zeros(size(S.clean)); set(snrText,'String',sprintf('%+.1f dB',get(snrSlider,'Value'))); % Vytvoření rušení; SNR se vztahuje k CELKOVÉMU přidanému rušení. % Stejný ovladač SNR platí pro sinusové rušení i bílý šum. interference=zeros(size(S.clean)); if get(noiseCheck,'Value') n=(0:numel(S.clean)-1)'/S.fs; tone=sin(2*pi*S.toneFrequency*n); interference=interference+tone-mean(tone); end if get(whiteNoiseCheck,'Value') % Pevné počáteční nastavení generátoru: při změně posuvníků zůstává šum stejný. stream=RandStream('mt19937ar','Seed',104); wn=randn(stream,numel(S.clean),1); interference=interference+wn-mean(wn); end signalRMS=sqrt(mean(S.clean.^2)); noiseRMS=sqrt(mean(interference.^2)); if signalRMS>0 && noiseRMS>0 interference=interference*(signalRMS/noiseRMS)*10^(-S.toneSNR/20); S.addedNoise=interference; S.x=S.clean+interference; end showSpectrogram(ax1,S.x,S.fs,'INPUT'); cla(ax2); title(ax2,'PROCESSED (press PROCESS)'); end function effectChanged(~,evt) S.effect=evt.NewValue.Tag; switch S.effect case 'STRETCH', set(sl(1),'Value',(1-0.2)/(5-0.2)); case 'PITCH', set(sl(1),'Value',.5); case 'DENOISE', set(sl(1),'Value',.45); case 'BLUR', set(sl(1),'Value',.25); end refreshControls(); S.y=[]; cla(ax2); title(ax2,'PROCESSED (press PROCESS)'); end function sliderChanged(~,~) refreshControls(); end function windowChanged(~,~) idx=max(1,min(numel(windowChoices),round(get(windowSlider,'Value')))); set(windowSlider,'Value',idx); refreshControls(); S.y=[]; cla(ax2); title(ax2,'PROCESSED (press PROCESS)'); end function refreshWindowInfo() variable=ismember(S.effect,{'STRETCH','PITCH','ROBOT','WHISPER','BLUR'}); if variable idx=max(1,min(numel(windowChoices),round(get(windowSlider,'Value')))); set(windowSlider,'Value',idx,'Enable','on'); S.winLength=windowChoices(idx); set(windowLabel,'String','WINDOW LENGTH'); else S.winLength=1024; set(windowSlider,'Enable','off'); set(windowLabel,'String','WINDOW (FIXED)'); end S.hop=S.winLength/4; set(windowInfo,'String',sprintf('N = %d samples | %.1f ms | overlap = 75 %% | hop = %d (%.2f ms)', ... S.winLength,1000*S.winLength/S.fs,S.hop,1000*S.hop/S.fs)); end function refreshControls() refreshWindowInfo(); switch S.effect case 'FILTER' set(paramTitle,'String','FILTER: hard zeroing of spectral bins (notch)'); set(nameText(1),'String','CENTER FREQUENCY'); set(nameText(2),'String','HALF-WIDTH'); set(sl(1),'Enable','on'); set(sl(2),'Enable','on'); f0=round(100+3900*get(sl(1),'Value')); bw=round(5+295*get(sl(2),'Value')); set(valText(1),'String',sprintf('%d Hz',f0)); % Počet frekvenčních binů s nezápornou frekvencí podle masky FILTER. % Záporné frekvence (sdružené protějšky) se nezapočítávají podruhé. hz=(0:S.winLength/2)'*S.fs/S.winLength; nZero=sum(abs(hz-(100+3900*get(sl(1),'Value'))) <= (5+295*get(sl(2),'Value'))); set(valText(2),'String',sprintf('+/- %d Hz | %d bins zeroed',bw,nZero)); case 'ROBOT' set(paramTitle,'String','ROBOT: replace STFT phases by zero'); set(nameText(1),'String','NOT USED'); set(nameText(2),'String','NOT USED'); set(sl(1),'Enable','off'); set(sl(2),'Enable','off'); set(valText(1),'String','-'); set(valText(2),'String','-'); case 'WHISPER' set(paramTitle,'String','WHISPER: randomize STFT phases'); set(nameText(1),'String','NOT USED'); set(nameText(2),'String','NOT USED'); set(sl(1),'Enable','off'); set(sl(2),'Enable','off'); set(valText(1),'String','-'); set(valText(2),'String','-'); case 'BLUR' set(paramTitle,'String','BLUR: smooth magnitude spectra across time (phases unchanged)'); set(nameText(1),'String','BLUR RADIUS'); set(nameText(2),'String','NOT USED'); set(sl(1),'Enable','on'); set(sl(2),'Enable','off'); radius=round(20*get(sl(1),'Value')); set(valText(1),'String',sprintf('%d frames each side (%.1f ms)',radius,1000*radius*S.hop/S.fs)); set(valText(2),'String','-'); case 'STRETCH' set(paramTitle,'String','STRETCH: phase vocoder, duration changes without pitch shift'); set(nameText(1),'String','DURATION RATIO'); set(nameText(2),'String','NOT USED'); set(sl(1),'Enable','on'); set(sl(2),'Enable','off'); set(valText(1),'String',sprintf('%.2f x',.2+4.8*get(sl(1),'Value'))); set(valText(2),'String','-'); case 'PITCH' set(paramTitle,'String','PITCH: phase vocoder + resampling, duration preserved'); set(nameText(1),'String','TRANSPOSITION'); set(nameText(2),'String','NOT USED'); set(sl(1),'Enable','on'); set(sl(2),'Enable','off'); set(valText(1),'String',sprintf('%+.1f semitones',-24+48*get(sl(1),'Value'))); set(valText(2),'String','-'); case 'DENOISE' set(paramTitle,'String','DENOISE: nonlinear spectral suppression (SYN04_vokoder_5)'); set(nameText(1),'String','SUPPRESSION STRENGTH'); set(nameText(2),'String','NONLINEAR COEFFICIENT c'); set(sl(1),'Enable','on'); set(sl(2),'Enable','off'); set(valText(1),'String',sprintf('%.1f x',.1+5.9*get(sl(1),'Value'))); coef=0.002+0.098*get(sl(1),'Value'); set(valText(2),'String',sprintf('c = %.4f',coef)); end set(nameText(3),'String','DRY / WET'); set(valText(3),'String',sprintf('%d %% processed',round(100*get(sl(3),'Value')))); end function processButton(~,~) if isempty(S.x), return; end set(status,'String','Processing ...'); drawnow; try if strcmp(S.effect,'STRETCH') ratio=.2+4.8*get(sl(1),'Value'); wet=phaseVocoderStretch(S.x,ratio,S.winLength,S.hop); elseif strcmp(S.effect,'PITCH') semitones=-24+48*get(sl(1),'Value'); r=2^(semitones/12); z=phaseVocoderStretch(S.x,r,S.winLength,S.hop); % Převzorkování prodlouženého signálu faktorem r obnoví původní délku. targetN=numel(S.x); query=(0:targetN-1)'*r+1; wet=interp1((1:numel(z))',z,query,'linear',0); else [X,win,hop,L] = analyze(S.x,S.winLength,S.hop); M=size(X,1); H=M/2+1; Y=X; switch S.effect case 'FILTER' f0=100+3900*get(sl(1),'Value'); bw=5+295*get(sl(2),'Value'); hz=(0:H-1)'*S.fs/M; % Ostrá spektrální zádrž: vynulování binů v okolí f0 ± bw. % Bez přechodového pásma (odpovídá snímku 4.9). mask=double(abs(hz-f0)>bw); Y(1:H,:)=X(1:H,:).*mask; Y(H+1:end,:)=conj(Y(H-1:-1:2,:)); case 'ROBOT' Y(1:H,:)=abs(X(1:H,:)); Y(H+1:end,:)=conj(Y(H-1:-1:2,:)); case 'WHISPER' phi=2*pi*rand(H,size(X,2)); phi(1,:)=0; phi(end,:)=0; Y(1:H,:)=abs(X(1:H,:)).*exp(1i*phi); Y(H+1:end,:)=conj(Y(H-1:-1:2,:)); case 'BLUR' radius=round(20*get(sl(1),'Value')); if radius>0 % Klouzavý průměr amplitud spektra v čase. % Fáze jednotlivých rámců se nemění. A=abs(X(1:H,:)); B=conv2(A,ones(1,2*radius+1),'same'); counts=conv2(ones(1,size(A,2)),ones(1,2*radius+1),'same'); B=B./counts; Y(1:H,:)=B.*exp(1i*angle(X(1:H,:))); Y(H+1:end,:)=conj(Y(H-1:-1:2,:)); end case 'DENOISE' % Stejné nelineární spektrální zesílení jako v SYN04_vokoder_5: % Y = X .* r ./ (r + coef), kde r = abs(X)/M. % Posuvník mění koeficient potlačení. % Není zapotřebí referenční záznam šumu. coef=0.002+0.098*get(sl(1),'Value'); r=abs(X)/M; Y=X.*(r./(r+coef)); end wet=synthesize(Y,win,hop,L); end wet=wet(:); if numel(wet)~=numel(S.x) && ~strcmp(S.effect,'STRETCH') wet=fitLength(wet,numel(S.x)); end % Vyrovnání špičkové úrovně zpracovaného signálu pro srovnatelný poslech. % Jde o kompenzaci úrovně, nikoli obnovu spektra. inPeak=max(abs(S.x)); wetPeak=max(abs(wet)); if wetPeak>1e-12 && inPeak>0 && ~strcmp(S.effect,'DENOISE') wet=wet*(inPeak/wetPeak); end mix=get(sl(3),'Value'); dry=fitLength(S.x,numel(wet)); S.y=(1-mix)*dry+mix*wet; % Společné zeslabení vstupu a výstupu při překročení plného rozsahu. % Zachování relativní hlasitosti při poslechovém porovnání bez přebuzení. commonPeak=max([1; abs(dry(:)); abs(S.y(:))]); S.playGain=1/commonPeak; S.dryPlay=dry*S.playGain; S.y=S.y*S.playGain; showSpectrogram(ax2,S.y,S.fs,['PROCESSED: ' S.effect]); set(status,'String',sprintf('%s processed | %.2f s | %d Hz',S.effect,numel(S.y)/S.fs,S.fs)); catch ME set(status,'String',['Processing error: ' ME.message]); end end function playOriginal(~,~) if isempty(S.x),return;end if ~isempty(S.y) && isfield(S,'playGain') playSignal(S.dryPlay); else playSignal(S.x/max(1,max(abs(S.x)))); end end function playResult(~,~) if isempty(S.y) set(status,'String','First press PROCESS.'); else playSignal(S.y); end end function playSignal(x) if isempty(x), return; end stopAudio([],[]); try % Normalizace špiček podobná soundsc pouze při přehrávání. % Neměníme S.x ani S.y (spektrogram a export WAV). peak=max(abs(x(:))); if peak>0 x=0.95*x/peak; end S.player=audioplayer(x,S.fs); play(S.player); catch ME set(status,'String',['Playback error: ' ME.message]); end end function stopAudio(~,~) if ~isempty(S.player) try, stop(S.player); catch, end end end function saveWav(~,~) if isempty(S.y) set(status,'String','First press PROCESS.'); return; end [f,p]=uiputfile('*.wav','Save processed audio','MKS_04b_processed.wav'); if isequal(f,0),return;end audiowrite(fullfile(p,f),S.y,S.fs); set(status,'String',['Saved: ' fullfile(p,f)]); end function closeFigure(~,~) stopAudio([],[]); delete(fig); end end function [X,w,hop,L]=analyze(x,M,hop) % STFT s doplněním nul na okrajích; odmocnina periodického Hannova okna pro WOLA. x=x(:); L=numel(x); w=sqrt(max(0,.5-.5*cos(2*pi*(0:M-1)'/M))); pad=M; xpad=[zeros(pad,1);x;zeros(pad+mod(-L,hop),1)]; K=floor((numel(xpad)-M)/hop)+1; X=zeros(M,K); for k=1:K idx=(k-1)*hop+(1:M); X(:,k)=fft(xpad(idx).*w); end end function y=synthesize(X,w,hop,L) M=numel(w); K=size(X,2); N=(K-1)*hop+M; y=zeros(N,1); weight=zeros(N,1); for k=1:K idx=(k-1)*hop+(1:M); frame=real(ifft(X(:,k))).*w; y(idx)=y(idx)+frame; weight(idx)=weight(idx)+w.^2; end y=y./max(weight,1e-10); if numel(y) interpolace amplitud -> akumulace fáze % -> inverzní FFT a vážené překrývání a sčítání rámců (WOLA). % Pevný krok analýzy i syntézy Ha; změna délky mění počet rámců. % Výukové zjednodušení: přímé rozdíly fází (bez rozbalování fáze). % 1. Krátkodobá Fourierova transformace (STFT) x=x(:); L=numel(x); pad=N; xp=[zeros(pad,1);x;zeros(pad+N,1)]; K=1+floor((numel(xp)-N)/Ha); w=sqrt(max(0,.5-.5*cos(2*pi*(0:N-1)'/N))); X=zeros(N,K); for k=1:K idx=(1:N)+(k-1)*Ha; X(:,k)=fft(xp(idx).*w); end % Použijeme nezáporné frekvence a obnovíme komplexně sdruženou symetrii. % 2. Interpolace amplitud na nové časové mřížce H=N/2+1; A=abs(X(1:H,:)); P=angle(X(1:H,:)); q=0:1/ratio:(K-1); if isempty(q),q=0;end B=interp1(0:K-1,A.',q.','linear').'; % 3. Kumulativní součet fázových přírůstků D = diff(P,1,2); % Rozdíly fází sousedních rámců phase = zeros(H,numel(q)); phase(:,1) = P(:,1); % Počáteční fáze if numel(q)>1 new_grid = min(size(D,2),floor(q(2:end))+1); D_new = D(:,new_grid); % Fázové přírůstky ve vybraných rámcích phase(:,2:end) = P(:,1) + cumsum(D_new,2); end % 4. Komplexní spektrum a sdružená symetrie Y=zeros(N,numel(q)); positive=B.*exp(1i*phase); positive(1,:)=real(positive(1,:)); positive(end,:)=real(positive(end,:)); Y(1:H,:)=positive; Y(H+1:end,:)=conj(positive(end-1:-1:2,:)); % 5. Inverzní FFT a vážené překrývání a sčítání rámců Kout=size(Y,2); outN=(Kout-1)*Ha+N; acc=zeros(outN,1); normW=zeros(outN,1); for k=1:Kout idx=(1:N)+(k-1)*Ha; frame=real(ifft(Y(:,k))).*w; acc(idx)=acc(idx)+frame; normW(idx)=normW(idx)+w.^2; end valid=normW>1e-10; acc(valid)=acc(valid)./normW(valid); start=pad+1; expected=max(1,round(L*ratio)); y=zeros(expected,1); last=min(outN,start+expected-1); if last>=start,y(1:last-start+1)=acc(start:last);end end