% MKS_03 - ADDITIVE SYNTHESIZER % % PATCH: BANJO / FLUTE / TRUMPET / ORGAN / TYMPANI / BELL / USER % HARMONY: SINGLE / MAJOR / MINOR % % Osm slideru = osm skutecnych parcial: % % x(t) = sum A_i(t) * sin(2*pi*r_i*f0*t) % % Volbou patche se nastavi frekvencni pomery r_i, amplitudy A_i % a charakteristicka obalka. Slidery umoznuji amplitudy dotunit. clear all; close all; clc keyboardType = 'QWERTZ'; if strcmpi(keyboardType,'QWERTZ') notes = 'awsedftgzhujk'; else notes = 'awsedftgyhujk'; end Fe = 22050; f0 = 220; fr = 2.^((0:12)/12)*f0; T = 2.0; %------------------------------------------------------------- % okno %------------------------------------------------------------- fig = figure('Name','MKS 03 - ADDITIVE SYNTHESIZER', ... 'NumberTitle','off','MenuBar','none','ToolBar','none', ... 'Position',[250 100 1000 680],'WindowKeyPressFcn',@keyPressed); axis([0 1 0 1]); axis off text(.5,.96,'MKS 03 - ADDITIVE SYNTHESIZER', ... 'HorizontalAlignment','center','FontSize',18,'FontWeight','bold'); text(.5,.88,upper(notes), ... 'HorizontalAlignment','center','FontSize',14); text(.5,.84,'Hrajte přímo na klávesnici', ... 'HorizontalAlignment','center','FontSize',10); %------------------------------------------------------------- % PATCH %------------------------------------------------------------- patchGroup = uibuttongroup(fig,'Title','PATCH','FontWeight','bold', ... 'Position',[.05 .53 .42 .23],'SelectionChangedFcn',@patchChanged); names = {'BANJO','FLUTE','TRUMPET','ORGAN','TYMPANI','BELL','USER'}; tags = {'banjo','flute','trumpet','organ','tympani','bell','user'}; for k=1:length(names) col=mod(k-1,2); row=floor((k-1)/2); uicontrol(patchGroup,'Style','radiobutton','String',names{k}, ... 'Position',[25+150*col 105-28*row 125 22], ... 'Tag',tags{k},'Value',k==1); end %------------------------------------------------------------- % HARMONY %------------------------------------------------------------- harmonyGroup = uibuttongroup(fig,'Title','HARMONY','FontWeight','bold', ... 'Position',[.56 .56 .30 .20],'SelectionChangedFcn',@harmonyChanged); uicontrol(harmonyGroup,'Style','radiobutton','String','SINGLE', ... 'Position',[25 70 120 22],'Tag','single','Value',1); uicontrol(harmonyGroup,'Style','radiobutton','String','MAJOR', ... 'Position',[25 43 120 22],'Tag','major'); uicontrol(harmonyGroup,'Style','radiobutton','String','MINOR', ... 'Position',[25 16 120 22],'Tag','minor'); patchText = uicontrol(fig,'Style','text', ... 'String','BANJO: harmonické parciály', ... 'Position',[530 335 360 24],'FontSize',10); %------------------------------------------------------------- % PARTIALS - 8 slideru %------------------------------------------------------------- uicontrol(fig,'Style','text','String','PARTIALS', ... 'Position',[80 300 820 25],'FontSize',11,'FontWeight','bold'); slider = gobjects(1,8); ratioText = gobjects(1,8); ampText = gobjects(1,8); for i=1:8 x = 80 + (i-1)*105; ratioText(i)=uicontrol(fig,'Style','text', ... 'String',sprintf('r%d = %g',i,i), ... 'Position',[x 265 90 20],'FontSize',9); slider(i)=uicontrol(fig,'Style','slider', ... 'Min',0,'Max',1,'Value',0, ... 'Position',[x+30 135 20 125], ... 'Callback',@sliderChanged); ampText(i)=uicontrol(fig,'Style','text', ... 'String','0.00', ... 'Position',[x 110 90 20],'FontSize',9); end %------------------------------------------------------------- % ENVELOPE - ATTACK / DECAY %------------------------------------------------------------- uicontrol(fig,'Style','text','String','ENVELOPE', ... 'Position',[60 75 110 22],'FontSize',10,'FontWeight','bold'); uicontrol(fig,'Style','text','String','ATTACK', ... 'Position',[60 50 60 18],'FontSize',9); attackSlider=uicontrol(fig,'Style','slider','Min',0.001,'Max',0.30, ... 'Value',0.01,'Position',[120 52 150 18],'Callback',@envelopeChanged); attackText=uicontrol(fig,'Style','text','String','0.010 s', ... 'Position',[275 49 65 20],'FontSize',9); uicontrol(fig,'Style','text','String','DECAY', ... 'Position',[60 25 60 18],'FontSize',9); decaySlider=uicontrol(fig,'Style','slider','Min',0.10,'Max',3.0, ... 'Value',0.35,'Position',[120 27 150 18],'Callback',@envelopeChanged); decayText=uicontrol(fig,'Style','text','String','0.35 s', ... 'Position',[275 24 65 20],'FontSize',9); %------------------------------------------------------------- % magnetofon %------------------------------------------------------------- uicontrol(fig,'Style','pushbutton','String','REC', ... 'Position',[430 35 90 35],'Callback',@recButton); uicontrol(fig,'Style','pushbutton','String','STOP', ... 'Position',[530 35 90 35],'Callback',@stopButton); uicontrol(fig,'Style','pushbutton','String','PLAY', ... 'Position',[630 35 90 35],'Callback',@playButton); uicontrol(fig,'Style','pushbutton','String','SAVE WAV', ... 'Position',[730 35 110 35],'Callback',@saveButton); statusText=uicontrol(fig,'Style','text', ... 'String','MAGNETOFON: připraven', ... 'Position',[500 75 350 25],'FontSize',10); %------------------------------------------------------------- % data %------------------------------------------------------------- data.notes=notes; data.fr=fr; data.Fe=Fe; data.T=T; data.patchType='banjo'; data.harmonyType='single'; data.slider=slider; data.ratioText=ratioText; data.ampText=ampText; data.patchText=patchText; data.statusText=statusText; data.attackSlider=attackSlider; data.decaySlider=decaySlider; data.attackText=attackText; data.decayText=decayText; data.recording=false; data.recordStart=[]; data.events={}; data.recordedSignal=[]; guidata(fig,data); % nacteni prvniho patche loadPatch(fig,'banjo'); %========================================================================== % 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 switch data.harmonyType case 'single' intervals=0; case 'major' intervals=[0 4 7]; case 'minor' intervals=[0 3 7]; end tone=zeros(1,round(data.T*data.Fe)); for n=1:length(intervals) f=data.fr(k)*2^(intervals(n)/12); % fletna zni o oktavu vyse if strcmp(data.patchType,'flute') f=2*f; end voice=additiveTone(f,data.T,data.Fe, ... data.r,data.A,data.attack,data.decay); tone=tone+voice; end m=max(abs(tone)); if m>0 tone=.95*tone/m; end sound(tone,data.Fe) 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=additiveTone(f,T,fs,r,A,attack,decay) t=0:1/fs:T-1/fs; out=zeros(size(t)); %------------------------------------------------------------- % obalka ATTACK / DECAY %------------------------------------------------------------- % rychly/pomaly nabeh podle ATTACK a exponencialni dozneni DECAY env=exp(-t/decay); Na=min(max(1,round(attack*fs)),length(t)); env(1:Na)=env(1:Na).*linspace(0,1,Na); %------------------------------------------------------------- % aditivni synteza %------------------------------------------------------------- for i=1:length(r) fi=r(i)*f; % ochrana proti aliasingu if fi0 out=.9*out/m; end end function patchChanged(src,event) fig=ancestor(src,'figure'); loadPatch(fig,event.NewValue.Tag); end function loadPatch(fig,patchType) data=guidata(fig); % Osm parcial pro kazdy patch. % U TYMPANI a BELL nejsou frekvencni pomery celociselne. switch patchType case 'banjo' r=[1 2 3 4 5 6 7 8]; A=[.0051 .0144 .0246 .0290 .0422 .0101 .0052 .0096]; A=A/max(A); attack=.005; decay=.35; txt='BANJO: harmonické parciály'; case 'flute' r=1:8; A=[1 .25 .063 .016 .008 .004 .002 .001]; attack=.10; decay=2.50; txt='FLUTE +1 OCT: harmonické parciály'; case 'trumpet' r=1:8; A=[1 1.8222 1.4444 .5556 .2222 .0889 .0667 .0444]; A=A/max(A); attack=.04; decay=1.20; txt='TRUMPET: harmonické parciály'; case 'organ' % zjednoduseny varhanni rejstrik, bez FM/Leslie a sumu r=[1 2 3 4 5 6 8 10]; A=[1 .80 .45 .60 .22 .18 .38 .10]; attack=.05; decay=3.00; txt='ORGAN: harmonické + rejstříkové složky'; case 'tympani' r=[1 1.5958 2.1417 2.3000 2.6583 2.9250 3.1625 3.5083]; A=[1 .4518 .2740 .2427 .1897 .1613 .1412 .1184]; attack=.005; decay=.85; txt='TYMPANI: NEHARMONICKÉ parciály'; case 'bell' r=[1 2 2.376 3.060 4 4.940 5.214 5.982]; A=[1 2.7143 1.4286 .4286 2 .2857 .7143 2.8571]; A=A/max(A); attack=.005; decay=1.50; txt='BELL: NEHARMONICKÉ parciály'; case 'user' r=1:8; A=[1 .5 .25 .12 .06 .03 .015 .008]; attack=.02; decay=.80; txt='USER: dotvořte vlastní spektrum'; end data.patchType=patchType; data.r=r; data.A=A; data.attack=attack; data.decay=decay; % naplneni slideru a popisku for i=1:8 set(data.slider(i),'Value',A(i)); set(data.ratioText(i),'String',sprintf('r%d = %.3g',i,r(i))); set(data.ampText(i),'String',sprintf('%.2f',A(i))); end set(data.patchText,'String',txt); set(data.attackSlider,'Value',attack); set(data.decaySlider,'Value',decay); set(data.attackText,'String',sprintf('%.3f s',attack)); set(data.decayText,'String',sprintf('%.2f s',decay)); guidata(fig,data); end function sliderChanged(src,~) fig=ancestor(src,'figure'); data=guidata(fig); for i=1:8 data.A(i)=get(data.slider(i),'Value'); set(data.ampText(i),'String',sprintf('%.2f',data.A(i))); end guidata(fig,data); end function envelopeChanged(src,~) fig=ancestor(src,'figure'); data=guidata(fig); data.attack=get(data.attackSlider,'Value'); data.decay=get(data.decaySlider,'Value'); set(data.attackText,'String',sprintf('%.3f s',data.attack)); set(data.decayText,'String',sprintf('%.2f s',data.decay)); guidata(fig,data); end function harmonyChanged(src,event) fig=ancestor(src,'figure'); data=guidata(fig); data.harmonyType=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 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 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_03_recording.wav',data.recordedSignal,data.Fe); set(data.statusText,'String','MAGNETOFON: uloženo MKS_03_recording.wav'); end