function MKS_05a_v12_fixed % MKS_05a - SUBTRACTIVE SYNTHESISER (device-paced audio) % Based on MKS_03. One real zero and one real pole, optionally modulated. % Run: MKS_05a_v12_fixed keyboardType = 'QWERTZ'; if strcmpi(keyboardType,'QWERTZ') notes = 'awsedftgzhujk'; else notes = 'awsedftgyhujk'; end fs=48000; f0=220; fr=f0*2.^((0:12)/12); blockN=512; fig=figure('Name','MKS 05a - SUBTRACTIVE SYNTHESISER', ... 'NumberTitle','off','MenuBar','none','ToolBar','none', ... 'Color',[.96 .96 .96],'Position',[75 55 1500 850], ... 'WindowKeyPressFcn',@keyPressed, ... 'WindowKeyReleaseFcn',@keyReleased,'CloseRequestFcn',@closeWindow); uicontrol(fig,'Style','text','String','MKS 05a - SUBTRACTIVE SYNTHESISER', ... 'FontSize',17,'FontWeight','bold','BackgroundColor',get(fig,'Color'), ... 'Position',[300 803 900 34]); uicontrol(fig,'Style','text','String',['KEYBOARD: ' upper(notes) ' (hold keys to play)'], ... 'FontSize',12,'BackgroundColor',get(fig,'Color'),'Position',[400 776 730 25]); sourceGroup=uibuttongroup(fig,'Title','SOURCE','FontWeight','bold', ... 'Units','pixels','Position',[25 548 310 207],'SelectionChangedFcn',@sourceChanged); srcNames={'SINE','SAW','TRIANGLE','PULSE','NOISE'}; for k=1:numel(srcNames) uicontrol(sourceGroup,'Style','radiobutton','String',srcNames{k}, ... 'Position',[12 151-(k-1)*29 95 23],'Tag',lower(srcNames{k}), ... 'Value',k==2); end uicontrol(fig,'Style','text','String','PULSE WIDTH', ... 'Position',[28 510 125 20],'BackgroundColor',get(fig,'Color'), ... 'HorizontalAlignment','left'); hWidth=uicontrol(fig,'Style','slider','Min',.05,'Max',.95,'Value',.5, ... 'Position',[157 509 125 22],'Callback',@controlChanged); hWidthText=uicontrol(fig,'Style','text','String','50 %','Position',[284 509 50 22], ... 'BackgroundColor',get(fig,'Color')); hSourceAxes=axes('Parent',sourceGroup,'Units','pixels','Position',[145 46 150 120]); filterPanel=uipanel(fig,'Title','ONE ZERO + ONE POLE (real axis)', ... 'FontWeight','bold','Units','pixels','Position',[355 548 735 207]); uicontrol(filterPanel,'Style','text','String','ZERO','FontWeight','bold', ... 'Position',[18 130 60 25],'HorizontalAlignment','left'); hZero=uicontrol(filterPanel,'Style','slider','Min',-1,'Max',1,'Value',0, ... 'Position',[93 133 540 24],'Callback',@controlChanged); hZeroText=uicontrol(filterPanel,'Style','text','String','0.00', ... 'Position',[644 130 65 26]); uicontrol(filterPanel,'Style','text','String','POLE','FontWeight','bold', ... 'Position',[18 68 60 25],'HorizontalAlignment','left'); hPole=uicontrol(filterPanel,'Style','slider','Min',-.99,'Max',.99,'Value',0, ... 'Position',[93 72 540 24],'Callback',@controlChanged); hPoleText=uicontrol(filterPanel,'Style','text','String','0.00', ... 'Position',[644 69 65 26]); uicontrol(filterPanel,'Style','text','String','-1','Position',[88 107 30 17]); uicontrol(filterPanel,'Style','text','String','0','Position',[357 107 30 17]); uicontrol(filterPanel,'Style','text','String','+1','Position',[605 107 35 17]); uicontrol(filterPanel,'Style','text','String','-1','Position',[88 45 30 17]); uicontrol(filterPanel,'Style','text','String','0','Position',[357 45 30 17]); uicontrol(filterPanel,'Style','text','String','+1','Position',[605 45 35 17]); modPanel=uipanel(fig,'Title','TIME-VARYING FILTER','FontWeight','bold', ... 'Units','pixels','Position',[355 398 735 133]); uicontrol(modPanel,'Style','text','String','TARGET', ... 'Position',[12 67 72 20],'HorizontalAlignment','left'); hTarget=uicontrol(modPanel,'Style','popupmenu', ... 'String',{'OFF','ZERO','POLE','BOTH'},'Position',[90 67 116 24], ... 'Callback',@controlChanged); uicontrol(modPanel,'Style','text','String','RATE (Hz)', ... 'Position',[223 68 82 20],'HorizontalAlignment','left'); hRate=uicontrol(modPanel,'Style','edit','String','0.5', ... 'Position',[312 66 75 26],'Callback',@controlChanged); uicontrol(modPanel,'Style','text','String','DEPTH', ... 'Position',[12 25 72 20],'HorizontalAlignment','left'); hDepth=uicontrol(modPanel,'Style','slider','Min',0,'Max',1,'Value',.3, ... 'Position',[90 24 295 23],'Callback',@controlChanged); hDepthText=uicontrol(modPanel,'Style','text','String','30 %', ... 'Position',[402 23 75 23]); envPanel=uipanel(fig,'Title','AMPLITUDE ENVELOPE (ADSR)','FontWeight','bold', ... 'Units','pixels','Position',[355 207 735 188]); labels={'ATTACK (s)','DECAY (s)','SUSTAIN (%)','RELEASE (s)'}; ys=[139 102 65 28]; for q=1:4 uicontrol(envPanel,'Style','text','String',labels{q}, ... 'Position',[12 ys(q) 100 22],'HorizontalAlignment','left'); end hAttack=uicontrol(envPanel,'Style','slider','Min',.005,'Max',.8,'Value',.03, ... 'Position',[118 140 255 22],'Callback',@controlChanged); hDecay=uicontrol(envPanel,'Style','slider','Min',.01,'Max',2,'Value',.25, ... 'Position',[118 103 255 22],'Callback',@controlChanged); hSustain=uicontrol(envPanel,'Style','slider','Min',0,'Max',1,'Value',.35, ... 'Position',[118 66 255 22],'Callback',@controlChanged); hRelease=uicontrol(envPanel,'Style','slider','Min',.01,'Max',2,'Value',.35, ... 'Position',[118 29 255 22],'Callback',@controlChanged); hAttackText=uicontrol(envPanel,'Style','text','String','0.030', ... 'Position',[383 139 65 22]); hDecayText=uicontrol(envPanel,'Style','text','String','0.250', ... 'Position',[383 102 65 22]); hSustainText=uicontrol(envPanel,'Style','text','String','35 %', ... 'Position',[383 65 65 22]); hReleaseText=uicontrol(envPanel,'Style','text','String','0.350', ... 'Position',[383 28 65 22]); hEnvelopeAxes=axes('Parent',envPanel,'Units','pixels', ... 'Position',[490 35 224 117]); harmonyGroup=uibuttongroup(fig,'Title','HARMONY','FontWeight','bold', ... 'Units','pixels','Position',[1135 213 320 111],'SelectionChangedFcn',@harmonyChanged); for k=1:3 names={'SINGLE','MAJOR','MINOR'}; uicontrol(harmonyGroup,'Style','radiobutton','String',names{k}, ... 'Tag',lower(names{k}),'Position',[15 70-26*(k-1) 140 22],'Value',k==1); end hZAxes=axes('Parent',fig,'Units','pixels','Position',[1140 562 310 174]); hFAxes=axes('Parent',fig,'Units','pixels','Position',[1140 342 310 174]); uicontrol(fig,'Style','pushbutton','String','REC','Position',[935 85 88 38], ... 'Callback',@recButton); uicontrol(fig,'Style','pushbutton','String','STOP','Position',[1031 85 88 38], ... 'Callback',@stopButton); uicontrol(fig,'Style','pushbutton','String','PLAY','Position',[1127 85 88 38], ... 'Callback',@playButton); uicontrol(fig,'Style','pushbutton','String','SAVE WAV','Position',[1223 85 125 38], ... 'Callback',@saveButton); hStatus=uicontrol(fig,'Style','text','String','MAGNETOFON: ready', ... 'Position',[940 43 405 28],'BackgroundColor',get(fig,'Color'), ... 'HorizontalAlignment','left'); hSpectrumAxes=axes('Parent',fig,'Units','pixels','Position',[60 253 275 230]); hUnderrun=uicontrol(fig,'Style','text','String','Underrun: 0 samples', ... 'Position',[1135 170 305 28],'BackgroundColor',get(fig,'Color'), ... 'HorizontalAlignment','left'); d.notes=notes; d.fr=fr; d.fs=fs; d.blockN=blockN; d.source='saw'; d.harmony='single'; d.recording=false; d.recordChunks={}; d.recordedSignal=[]; d.voices=struct([]); d.pressed=false(1,numel(notes)); d.player=[]; d.mousePending=false(1,numel(notes)); d.mouseReleaseAt=zeros(1,numel(notes)); d.sampleClock=0; d.writer=[]; d.running=true; d.totalUnderrun=0; d.audioError=false; d.hUnderrun=hUnderrun; d.hWidth=hWidth; d.hWidthText=hWidthText; d.hZero=hZero; d.hPole=hPole; d.hZeroText=hZeroText; d.hPoleText=hPoleText; d.hTarget=hTarget; d.hRate=hRate; d.hDepth=hDepth; d.hDepthText=hDepthText; d.hAttack=hAttack; d.hDecay=hDecay; d.hAttackText=hAttackText; d.hDecayText=hDecayText; d.hSustain=hSustain; d.hRelease=hRelease; d.hSustainText=hSustainText; d.hReleaseText=hReleaseText; d.hEnvelopeAxes=hEnvelopeAxes; d.hZAxes=hZAxes; d.hFAxes=hFAxes; d.hSourceAxes=hSourceAxes; d.hSpectrumAxes=hSpectrumAxes; d.hStatus=hStatus; d.spectrumN=4096; d.spectrumIn=zeros(d.spectrumN,1); d.spectrumOut=zeros(d.spectrumN,1); d.spectrumPos=0; d.spectrumCount=0; freq=(1:floor(d.spectrumN/2))'*fs/d.spectrumN; d.spectrumSourceLine=plot(hSpectrumAxes,freq,-90*ones(size(freq)), ... 'Color',[.65 .65 .65],'LineWidth',.9); hold(hSpectrumAxes,'on'); d.spectrumFilteredLine=plot(hSpectrumAxes,freq,-90*ones(size(freq)), ... 'Color',[.1 .35 .8],'LineWidth',1.2); hold(hSpectrumAxes,'off'); set(hSpectrumAxes,'XScale','log','FontSize',8); xlim(hSpectrumAxes,[80 12000]); ylim(hSpectrumAxes,[-85 35]); grid(hSpectrumAxes,'on'); title(hSpectrumAxes,'SOURCE / OUTPUT SPECTRUM','FontSize',10); xlabel(hSpectrumAxes,'Frequency (Hz)'); ylabel(hSpectrumAxes,'Relative level (dB)'); % Piano keyboard: white keys first, black keys on top. % Graphics are updated ONLY on key events. d.keyRects=gobjects(1,numel(notes)); d.keyBaseColors=zeros(numel(notes),3); keyPanel=uipanel(fig,'Title','KEYBOARD (A W S E D F T G Z H U J K)', ... 'FontWeight','bold','Units','pixels','Position',[25 23 870 125]); whiteIdx=[1 3 5 6 8 10 12 13]; blackIdx=[2 4 7 9 11]; whiteW=102; left=16; for j=1:numel(whiteIdx) k=whiteIdx(j); x=left+(j-1)*whiteW; d.keyRects(k)=uicontrol(keyPanel,'Style','pushbutton', ... 'String',upper(notes(k)),'FontSize',12,'FontWeight','bold', ... 'BackgroundColor',[1 1 1],'ForegroundColor',[.1 .1 .1], ... 'Position',[x 10 whiteW-2 86], ... 'Callback',{@mouseNote,k}); d.keyBaseColors(k,:)=[1 1 1]; end % C# D# F# G# A# between white keys 1/2, 2/3, 4/5, 5/6, 6/7 blackAfter=[1 2 4 5 6]; for j=1:numel(blackIdx) k=blackIdx(j); x=left+blackAfter(j)*whiteW-29; d.keyRects(k)=uicontrol(keyPanel,'Style','pushbutton', ... 'String',upper(notes(k)),'FontSize',10,'FontWeight','bold', ... 'BackgroundColor',[.13 .16 .20],'ForegroundColor',[1 1 1], ... 'Position',[x 43 58 53], ... 'Callback',{@mouseNote,k}); d.keyBaseColors(k,:)=[.13 .16 .20]; end guidata(fig,d); updateDisplay(fig); % Device-paced engine adapted from synthlab_polyphonic_v3: % no MATLAB timer; audioDeviceWriter provides the audio clock. if exist('audioDeviceWriter','class')~=8 && exist('audioDeviceWriter','file')~=2 set(hStatus,'String','Audio Toolbox: audioDeviceWriter unavailable'); warning('MKS_05a_v12_fixed requires Audio Toolbox.'); return end try try writer=audioDeviceWriter('SampleRate',fs,'BufferSize',blockN); catch writer=audioDeviceWriter('SampleRate',fs); end d.writer=writer; guidata(fig,d); catch ME set(hStatus,'String',['AUDIO ERROR: ' ME.message]); warning('%s',ME.message); return end lastUpdate=tic; try while isgraphics(fig) % Dispatch keyboard events without an independent audio timer. drawnow if ~isgraphics(fig), break, end d=guidata(fig); if ~d.running, break, end [y,d]=renderBlock(d); % Keep the limiter continuous across audio blocks. y=0.20*tanh(y/0.20); underrun=writer(y); if isempty(underrun), underrun=0; end d.totalUnderrun=d.totalUnderrun+double(underrun); if d.recording d.recordChunks{end+1}=y; end guidata(fig,d); if toc(lastUpdate)>0.75 set(hUnderrun,'String',sprintf('Underrun: %.0f samples',d.totalUnderrun)); if ~isempty(d.voices), updateSpectrum(fig,d); end lastUpdate=tic; end end catch ME if isgraphics(fig) set(hStatus,'String',['AUDIO ERROR: ' ME.message]); end fprintf(2, '\n=== MKS 05a AUDIO ERROR ===\n%s\n', getReport(ME,'extended','hyperlinks','off')); warning('MKS_05a_v12_fixed: %s',ME.message); end try, release(writer); catch, end % Keep GUI open after audio error for diagnosis. if isgraphics(fig) d=guidata(fig); d.running=false; d.writer=[]; guidata(fig,d); set(hStatus,'String','Audio stopped; see MATLAB Command Window for error.'); end end function keyPressed(fig,event) if ~ishandle(fig), return, end d=guidata(fig); key=decodeNoteKey(event); if numel(key)~=1 || ~ismember(key,d.notes), return, end k=find(d.notes==key,1); if isempty(k) || d.pressed(k) || isempty(d.writer), return, end d.pressed(k)=true; if isgraphics(d.keyRects(k)), set(d.keyRects(k),'BackgroundColor',[.35 .70 .95]); end switch d.harmony case 'single', intervals=0; case 'major', intervals=[0 4 7]; otherwise, intervals=[0 3 7]; end for i=1:numel(intervals) v=struct('key',k,'f',d.fr(k)*2^(intervals(i)/12), ... 'phase',0,'elapsed',0,'amp',0,'releasing',false, ... 'releaseAmp',0,'releaseElapsed',0,'xp',0,'yp',0); if isempty(d.voices), d.voices=v; else, d.voices(end+1)=v; end end guidata(fig,d); end function mouseNote(src,~,k) % Mouse click: short audition. Computer keyboard supports actual hold. fig=ancestor(src,'figure'); if ~ishandle(fig), return, end d=guidata(fig); if d.pressed(k) || isempty(d.writer) || ~d.running, return, end event=struct('Key',d.notes(k),'Character',d.notes(k)); keyPressed(fig,event); % A mouse button does not provide a reliable release callback in uicontrol. % End the click note via a short release using the next audio block counter. d=guidata(fig); d.mousePending(k)=true; d.mouseReleaseAt(k)=d.sampleClock+round(0.18*d.fs); guidata(fig,d); end function keyReleased(fig,event) if ~ishandle(fig), return, end d=guidata(fig); key=decodeNoteKey(event); if numel(key)~=1 || ~ismember(key,d.notes), return, end k=find(d.notes==key,1); if isempty(k), return, end d.pressed(k)=false; if isgraphics(d.keyRects(k)), set(d.keyRects(k),'BackgroundColor',d.keyBaseColors(k,:)); end for i=1:numel(d.voices) if d.voices(i).key==k && ~d.voices(i).releasing d.voices(i).releasing=true; d.voices(i).releaseElapsed=0; d.voices(i).releaseAmp=d.voices(i).amp; end end guidata(fig,d); end function key=decodeNoteKey(event) % Physical key identifier is more reliable for KEY UP than Character. key=''; try key=lower(char(event.Key)); catch end if numel(key)==1, return, end try ch=lower(char(event.Character)); if numel(ch)==1, key=ch; else, key=''; end catch key=''; end end function [y,d]=renderBlock(d) N=d.blockN; y=zeros(N,1); sourceMix=zeros(N,1); filteredMix=zeros(N,1); d.sampleClock=d.sampleClock+N; for k=find(d.mousePending & d.sampleClock>=d.mouseReleaseAt) d.mousePending(k)=false; d.pressed(k)=false; if isgraphics(d.keyRects(k)) set(d.keyRects(k),'BackgroundColor',d.keyBaseColors(k,:)); end for vi=1:numel(d.voices) if d.voices(vi).key==k && ~d.voices(vi).releasing d.voices(vi).releasing=true; d.voices(vi).releaseElapsed=0; d.voices(vi).releaseAmp=d.voices(vi).amp; end end end remaining=true(1,numel(d.voices)); for i=1:numel(d.voices) [voice,signal,done,sourceSignal,filteredSignal]=renderVoice(d.voices(i),d,N); d.voices(i)=voice; y=y+signal; sourceMix=sourceMix+sourceSignal; filteredMix=filteredMix+filteredSignal; remaining(i)=~done; end if ~isempty(d.voices), d.voices=d.voices(remaining); end % Rolling, pre-envelope spectra for a direct comparison of filtering. idx=mod(d.spectrumPos+(0:N-1),d.spectrumN)+1; d.spectrumIn(idx)=sourceMix; d.spectrumOut(idx)=filteredMix; d.spectrumPos=mod(d.spectrumPos+N,d.spectrumN); d.spectrumCount=min(d.spectrumN,d.spectrumCount+N); end function [v,y,done,sourceSignal,filteredSignal]=renderVoice(v,d,N) fs=d.fs; t=(0:N-1)'/fs; phase=v.phase+2*pi*v.f*t; width=get(d.hWidth,'Value'); switch d.source case 'sine' x=sin(phase); case 'saw' x=zeros(N,1); H=min(floor((fs/2-1)/v.f),16); for h=1:H, x=x+(-1)^(h+1)*sin(h*phase)/h; end x=x*2/pi; case 'triangle' x=zeros(N,1); H=min(floor((fs/2-1)/v.f),16); for h=1:2:H x=x+(-1)^((h-1)/2)*sin(h*phase)/(h*h); end x=x*8/pi^2; case 'pulse' x=zeros(N,1); H=min(floor((fs/2-1)/v.f),16); for h=1:H x=x+2*sin(pi*h*width)/(pi*h)*cos(h*phase-pi*h*width); end otherwise x=randn(N,1)*0.3; end v.phase=mod(v.phase+2*pi*v.f*N/fs,2*pi); z0=get(d.hZero,'Value'); p0=get(d.hPole,'Value'); target=get(d.hTarget,'Value'); depth=get(d.hDepth,'Value'); % Fast path: constant one-zero/one-pole filter using compiled MATLAB filter(). % The single DF-II state equals p*previousOutput - z*previousInput. if target==1 || depth==0 zi=p0*v.yp-z0*v.xp; [y,~]=filter([1 -z0],[1 -p0],x,zi); else rate=str2double(get(d.hRate,'String')); if ~isfinite(rate), rate=0; end rate=max(0,min(20,rate)); if rate==0 zi=p0*v.yp-z0*v.xp; [y,~]=filter([1 -z0],[1 -p0],x,zi); else tAbsMod=v.elapsed+t; modWave=sin(2*pi*rate*tAbsMod); zv=z0*ones(N,1); pv=p0*ones(N,1); if target==2 || target==4 zv=zv+depth*(1-abs(z0))*modWave; end if target==3 || target==4 pv=pv+depth*(0.99-abs(p0))*modWave; end zv=max(-1,min(1,zv)); pv=max(-.99,min(.99,pv)); y=zeros(N,1); xp=v.xp; yp=v.yp; for n=1:N yn=x(n)-zv(n)*xp+pv(n)*yp; y(n)=yn; xp=x(n); yp=yn; end end end v.xp=x(end); v.yp=y(end); sourceSignal=x; filteredSignal=y; attack=get(d.hAttack,'Value'); decay=get(d.hDecay,'Value'); sustain=get(d.hSustain,'Value'); releaseTime=get(d.hRelease,'Value'); % Piecewise linear ADSR; RELEASE begins at the actual current level. tAbs=v.elapsed+t; heldEnv=adsrHeld(tAbs,attack,decay,sustain); if v.releasing env=v.releaseAmp*max(0,1-(v.releaseElapsed+t)/releaseTime); v.releaseElapsed=v.releaseElapsed+N/fs; else env=heldEnv; end v.amp=env(end); v.elapsed=v.elapsed+N/fs; y=y.*env; done=v.releasing && v.releaseElapsed>=releaseTime; end function controlChanged(src,~) fig=ancestor(src,'figure'); updateDisplay(fig); end function sourceChanged(src,event) fig=ancestor(src,'figure'); d=guidata(fig); d.source=event.NewValue.Tag; guidata(fig,d); updateDisplay(fig); end function harmonyChanged(src,event) fig=ancestor(src,'figure'); d=guidata(fig); d.harmony=event.NewValue.Tag; guidata(fig,d); end function updateDisplay(fig) if ~ishandle(fig), return, end d=guidata(fig); z=get(d.hZero,'Value'); p=get(d.hPole,'Value'); set(d.hZeroText,'String',sprintf('%+.2f',z)); set(d.hPoleText,'String',sprintf('%+.2f',p)); set(d.hWidthText,'String',sprintf('%.0f %%',100*get(d.hWidth,'Value'))); set(d.hDepthText,'String',sprintf('%.0f %%',100*get(d.hDepth,'Value'))); set(d.hAttackText,'String',sprintf('%.3f',get(d.hAttack,'Value'))); set(d.hDecayText,'String',sprintf('%.3f',get(d.hDecay,'Value'))); set(d.hSustainText,'String',sprintf('%.0f %%',100*get(d.hSustain,'Value'))); set(d.hReleaseText,'String',sprintf('%.3f',get(d.hRelease,'Value'))); updateEnvelopePreview(d); if isempty(d.voices), updateSourcePeriod(d); end if strcmp(d.source,'pulse'), set(d.hWidth,'Enable','on'); else, set(d.hWidth,'Enable','off'); end if ~isempty(d.voices), return, end ax=d.hZAxes; cla(ax); th=linspace(0,2*pi,300); plot(ax,cos(th),sin(th),'Color',[.6 .6 .6]); hold(ax,'on'); plot(ax,[-1.1 1.1],[0 0],'k:'); plot(ax,[0 0],[-1.1 1.1],'k:'); plot(ax,z,0,'ob','MarkerSize',9,'LineWidth',2); plot(ax,p,0,'xr','MarkerSize',10,'LineWidth',2); hold(ax,'off'); axis(ax,'equal'); axis(ax,[-1.12 1.12 -1.12 1.12]); title(ax,'z-plane: o = zero, x = pole','FontSize',10); xlabel(ax,'Re(z)'); ylabel(ax,'Im(z)'); grid(ax,'on'); f=linspace(0,d.fs/2,600); w=2*pi*f/d.fs; H=(1-z*exp(-1i*w))./(1-p*exp(-1i*w)); mag=20*log10(max(abs(H),1e-5)); ax=d.hFAxes; plot(ax,f,mag,'LineWidth',1.3); grid(ax,'on'); xlim(ax,[0 d.fs/2]); ylim(ax,[-45 45]); title(ax,'Static frequency response','FontSize',10); xlabel(ax,'Frequency (Hz)'); ylabel(ax,'dB'); end function updateEnvelopePreview(d) % ADSR preview: illustrative key-up after attack + decay + 0.3 s. ax=d.hEnvelopeAxes; attack=get(d.hAttack,'Value'); decay=get(d.hDecay,'Value'); sustain=get(d.hSustain,'Value'); releaseTime=get(d.hRelease,'Value'); keyUp=attack+decay+0.3; tHeld=linspace(0,keyUp,450); aHeld=adsrHeld(tHeld,attack,decay,sustain); tRelease=linspace(0,releaseTime,140); aRelease=aHeld(end)*max(0,1-tRelease/releaseTime); plot(ax,tHeld,aHeld,'Color',[.1 .4 .8],'LineWidth',1.4); hold(ax,'on'); plot(ax,keyUp+tRelease,aRelease,'Color',[.85 .3 .15],'LineWidth',1.5); plot(ax,[keyUp keyUp],[0 1.05],':','Color',[.5 .5 .5]); hold(ax,'off'); xlim(ax,[0 keyUp+releaseTime]); ylim(ax,[0 1.05]); set(ax,'FontSize',7,'YTick',[0 .5 1]); grid(ax,'on'); title(ax,'A / D / S / R','FontSize',8); end function a=adsrHeld(t,attack,decay,sustain) % Shared envelope formula for the sound engine and preview. a=zeros(size(t)); iA=t=attack & t=attack+decay)=sustain; end function updateSourcePeriod(d) ax=d.hSourceAxes; phase=linspace(0,2*pi,501); width=get(d.hWidth,'Value'); switch d.source case 'sine' x=sin(phase); case 'saw' x=2*mod(phase/(2*pi),1)-1; x(end)=1; case 'triangle' q=phase/(2*pi); x=1-4*abs(q-0.5); case 'pulse' x=2*(phase/(2*pi)