% MKS_02 - WAVETABLE SYNTHESIZER % % Navazuje na MKS_01: % - hrajeme stejnymi klavesami % - zachovavame magnetofon REC / STOP / PLAY / SAVE WAV % % Nove: % WAVETABLE: SINUS / BANJO / VLASTNI % ENVELOPE : EXP / ADSR % % Pri kazdem stisku klavesy se ton vytvori z aktualne zvolene % wavetable a aktualne zvolene obalky. % % Pro volbu BANJO je potreba soubor banjo.wav ve stejne slozce. clear all close all clc %------------------------------------------------------------- % zakladni nastaveni %------------------------------------------------------------- keyboardType = 'QWERTZ'; if strcmpi(keyboardType,'QWERTZ') notes = 'awsedftgzhujk'; else notes = 'awsedftgyhujk'; end Fe = 22050; f0 = 220; ht = 2^(1/12); fr = ht.^(0:12)*f0; T = 0.7; %------------------------------------------------------------- % WAVETABLES %------------------------------------------------------------- % 1) sinus - jedna perioda Ntable = 128; P_sinus = sin(2*pi*(0:Ntable-1)/Ntable); % 2) banjo - jedna perioda z realne nahravky [xBanjo,fsBanjo] = audioread('banjo.wav'); if size(xBanjo,2) > 1 xBanjo = mean(xBanjo,2); end % stejny usek jako ve starsim SYN LAB P_banjo = xBanjo(144:172)'; P_banjo = P_banjo - mean(P_banjo); % 3) vlastni wavetable % ZMENTE JI PODLE SEBE: P_user = sawtooth(2*pi*(0:Ntable-1)/Ntable); %------------------------------------------------------------- % okno %------------------------------------------------------------- fig = figure( ... 'Name','MKS 02 - WAVETABLE SYNTHESIZER', ... 'NumberTitle','off', ... 'MenuBar','none', ... 'ToolBar','none', ... 'Position',[400 250 760 430], ... 'WindowKeyPressFcn',@keyPressed); axis([0 1 0 1]) axis off text(0.5,0.92,'MKS 02 - WAVETABLE SYNTHESIZER', ... 'HorizontalAlignment','center','FontSize',18,'FontWeight','bold'); text(0.5,0.82,upper(notes), ... 'HorizontalAlignment','center','FontSize',14); text(0.5,0.76,'Hrajte přímo na klávesnici', ... 'HorizontalAlignment','center','FontSize',10); %------------------------------------------------------------- % panel WAVETABLE %------------------------------------------------------------- waveGroup = uibuttongroup(fig, ... 'Title','WAVETABLE', ... 'FontWeight','bold', ... 'Position',[0.12 0.36 0.30 0.30], ... 'SelectionChangedFcn',@waveChanged); uicontrol(waveGroup,'Style','radiobutton', ... 'String','SINUS', ... 'Position',[25 75 120 25], ... 'Tag','sinus', ... 'Value',1); uicontrol(waveGroup,'Style','radiobutton', ... 'String','BANJO', ... 'Position',[25 45 120 25], ... 'Tag','banjo'); uicontrol(waveGroup,'Style','radiobutton', ... 'String','VLASTNÍ', ... 'Position',[25 15 120 25], ... 'Tag','user'); %------------------------------------------------------------- % panel ENVELOPE %------------------------------------------------------------- envGroup = uibuttongroup(fig, ... 'Title','ENVELOPE', ... 'FontWeight','bold', ... 'Position',[0.58 0.36 0.30 0.30], ... 'SelectionChangedFcn',@envChanged); uicontrol(envGroup,'Style','radiobutton', ... 'String','EXP', ... 'Position',[25 60 120 25], ... 'Tag','exp', ... 'Value',1); uicontrol(envGroup,'Style','radiobutton', ... 'String','ADSR', ... 'Position',[25 25 120 25], ... 'Tag','adsr'); %------------------------------------------------------------- % magnetofon %------------------------------------------------------------- uicontrol(fig,'Style','pushbutton', ... 'String','REC', ... 'Position',[145 45 90 35], ... 'Callback',@recButton); uicontrol(fig,'Style','pushbutton', ... 'String','STOP', ... 'Position',[250 45 90 35], ... 'Callback',@stopButton); uicontrol(fig,'Style','pushbutton', ... 'String','PLAY', ... 'Position',[355 45 90 35], ... 'Callback',@playButton); uicontrol(fig,'Style','pushbutton', ... 'String','SAVE WAV', ... 'Position',[460 45 110 35], ... 'Callback',@saveButton); statusText = uicontrol(fig,'Style','text', ... 'String','MAGNETOFON: připraven', ... 'Position',[205 92 350 25], ... 'FontSize',10); %------------------------------------------------------------- % data programu %------------------------------------------------------------- data.notes = notes; data.fr = fr; data.Fe = Fe; data.T = T; data.P_sinus = P_sinus; data.P_banjo = P_banjo; data.P_user = P_user; data.waveType = 'sinus'; data.envType = 'exp'; data.recording = false; data.recordStart = []; data.events = {}; % kazdy zaznam obsahuje cas + hotovy ton data.recordedSignal = []; data.statusText = statusText; guidata(fig,data); %========================================================================== % lokalni funkce %========================================================================== function keyPressed(src,event) data = guidata(src); key = lower(event.Character); if numel(key) ~= 1 return end k = find(data.notes == key,1); if isempty(k) return end %------------------------------------------------------------- % zvolena wavetable %------------------------------------------------------------- switch data.waveType case 'sinus' P = data.P_sinus; case 'banjo' P = data.P_banjo; case 'user' P = data.P_user; end %------------------------------------------------------------- % vytvoreni tonu %------------------------------------------------------------- tone = wavetableTone(P,data.fr(k),data.T,data.Fe,data.envType); sound(tone,data.Fe) %------------------------------------------------------------- % pokud bezi zaznam, ulozime CAS + HOTOVY TON % tim se zachovaji i zmeny WAVETABLE a ENVELOPE behem hrani %------------------------------------------------------------- if data.recording tNow = toc(data.recordStart); data.events{end+1,1} = tNow; data.events{end,2} = tone; guidata(src,data); end end function out = wavetableTone(P,f,T,fs,envType) %------------------------------------------------------------- % WAVETABLE SYNTHESIS %------------------------------------------------------------- P = P(:)'; N = length(P); n = 0:round(T*fs)-1; % spojita pozice v tabulce ind = mod(n*f*N/fs,N) + 1; % linearni interpolace i1 = floor(ind); i2 = mod(i1,N) + 1; a = ind-i1; out = (1-a).*P(i1) + a.*P(i2); %------------------------------------------------------------- % ENVELOPE %------------------------------------------------------------- t = (0:length(out)-1)/fs; switch envType case 'exp' tau = 0.18; envelope = exp(-t/tau); case 'adsr' % prednastavene hodnoty ADSR A = 0.08; % attack [s] D = 0.12; % decay [s] S = 0.65; % sustain level R = 0.18; % release [s] envelope = ADSR(length(out),fs,A,D,S,R); end out = envelope .* out; % bezpecna normalizace m = max(abs(out)); if m > 1 out = out/m; end end function envelope = ADSR(N,fs,A,D,S,R) NA = round(A*fs); ND = round(D*fs); NR = round(R*fs); % zbytek tonu je sustain NS = N - NA - ND - NR; if NS < 0 % kdyby byl ton prilis kratky, zkratime jednotlive faze scale = N/(NA+ND+NR); NA = floor(NA*scale); ND = floor(ND*scale); NR = floor(NR*scale); NS = N - NA - ND - NR; end attack = linspace(0,1,max(NA,1)); decay = linspace(1,S,max(ND,1)); sustain = S*ones(1,max(NS,0)); release = linspace(S,0,max(NR,1)); envelope = [attack decay sustain release]; % presna delka N if length(envelope) > N envelope = envelope(1:N); elseif length(envelope) < N envelope(end+1:N) = 0; end end function waveChanged(src,event) fig = ancestor(src,'figure'); data = guidata(fig); data.waveType = event.NewValue.Tag; guidata(fig,data); end function envChanged(src,event) fig = ancestor(src,'figure'); data = guidata(fig); data.envType = event.NewValue.Tag; guidata(fig,data); end function recButton(src,~) fig = ancestor(src,'figure'); data = guidata(fig); data.recording = true; data.recordStart = tic; data.events = {}; data.recordedSignal = []; set(data.statusText,'String','MAGNETOFON: ● REC'); guidata(fig,data); end function stopButton(src,~) fig = ancestor(src,'figure'); data = guidata(fig); data.recording = false; if isempty(data.events) set(data.statusText,'String','MAGNETOFON: žádný záznam'); guidata(fig,data); return end %------------------------------------------------------------- % slozeni zaznamu %------------------------------------------------------------- lastStart = data.events{end,1}; lastTone = data.events{end,2}; N = round(lastStart*data.Fe) + length(lastTone); y = zeros(1,N); for n = 1:size(data.events,1) t0 = data.events{n,1}; tone = data.events{n,2}; i1 = round(t0*data.Fe) + 1; i2 = min(i1 + length(tone) - 1,N); L = i2-i1+1; y(i1:i2) = y(i1:i2) + tone(1:L); end % normalizace pri soubehu tonu m = max(abs(y)); if m > 1 y = y/m; end data.recordedSignal = y; set(data.statusText,'String','MAGNETOFON: ■ STOP'); guidata(fig,data); end function playButton(src,~) fig = ancestor(src,'figure'); data = guidata(fig); if isempty(data.recordedSignal) set(data.statusText,'String','MAGNETOFON: nejprve nahrajte sekvenci'); return end set(data.statusText,'String','MAGNETOFON: ▶ PLAY'); sound(data.recordedSignal,data.Fe); end function saveButton(src,~) fig = ancestor(src,'figure'); data = guidata(fig); if isempty(data.recordedSignal) set(data.statusText,'String','MAGNETOFON: není co uložit'); return end audiowrite('MKS_02_recording.wav',data.recordedSignal,data.Fe); set(data.statusText,'String','MAGNETOFON: uloženo MKS_02_recording.wav'); end