Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
classdef WidgetAudioStimuli < handle
%WIDGETAUDIOSTIMULI generates user defined audio stimuli
%
% GUI Widget for
% user defined audio stimuli
% choose between monotones, linear or exponential sweeps
% load external audio file
% combine audio stimuli in trains
% plot frequency/time spectrogram
%
% requires:
% uiGridLayout.m
%
% Georgi Tushev
% sciclist@brain.mpg.de
% Max-Planck Institute For Brain Research
%
properties
audio
end
properties (Access = private)
duration
freqBase
freqThresh
pulses
offset
tdata
ydata
extern
tune
end
properties (Access = protected)
ui_parent
ui_grid
ui_panel
ui_edit_duration
ui_edit_freqBase
ui_edit_freqThresh
ui_edit_pulses
ui_edit_offset
ui_popup_chirp
ui_checkbox_train
ui_checkbox_extern
ui_pushbutton_load
ui_pushbutton_spectrum
ui_pushbutton_play
ui_text_extern
ui_spectrogram
end
properties (Constant = true, Access = private, Hidden = true)
UIWINDOW_SIZE = [1, 1, 256, 211];
GRID_VGAP = [12, 2, 5];
GRID_HGAP = [5, 2, 5];
BACKGROUND_COLOR = [1, 1, 1];
FOREGROUND_COLOR = [0.5, 0.5, 0.5];
PUSHBUTTON_SIZE = [1, 1, 90, 26];
EDITBOX_SIZE = [1, 1, 60, 20];
POPUP_SIZE = [1, 1, 90, 20];
CHECKBOX_SIZE = [1, 1, 90, 20];
CHIRP_TYPE = {'pure', 'linear', 'exp'};
SAMPLING_FREQ = 44100;
FADE_TIME = 0.05;
MIN_FREQUENCY = 1000;
MAX_FREQUENCY = 25000;
MAX_PULSES = 50;
end
%% --- constructor / destructor --- %%
methods
function obj = WidgetAudioStimuli(varargin)
%WIDGETAUDIOSTIMULI class constructor
parserObj = inputParser;
addParameter(parserObj, 'Parent', [], @isgraphics);
parse(parserObj, varargin{:});
if isempty(parserObj.Results.Parent)
obj.ui_parent = figure(...
'Visible', 'on',...
'Tag', 'hAudioStimuli',...
'Name', 'Audio Stimuli',...
'MenuBar', 'none',...
'ToolBar', 'none',...
'NumberTitle', 'off',...
'Color', obj.BACKGROUND_COLOR,...
'Resize', 'off',...
'Units', 'pixels',...
'Position', obj.UIWINDOW_SIZE,...
'CloseRequestFcn', @obj.fcnCallback_close);
movegui(obj.ui_parent, 'northwest');
else
obj.ui_parent = parserObj.Results.Parent;
end
% set default properties
obj.defaults();
% render user interface
obj.render();
% compose audio
obj.compose();
end
function obj = dispose(obj)
%DISPOSE class destructor
% remove spectrogram figures
if isgraphics(obj.ui_spectrogram, 'figure')
delete(obj.ui_spectrogram);
end
% remove audio player object
if isa(obj.audio, 'audioplayer')
delete(obj.audio);
end
% remove user interface grid
if isa(obj.ui_grid, 'uiGridLayout')
delete(obj.ui_grid);
end
% check if parent is figure or was inherit
if isgraphics(obj.ui_parent, 'figure')
delete(obj.ui_parent);
end
% dispose class object
delete(obj);
end
function obj = defaults(obj)
%DEFAULTS set default values for class properties
% default properties
obj.duration = 0.1;
obj.freqBase = 5000;
obj.freqThresh = 15000;
obj.pulses = 5;
obj.offset = 0.1;
obj.tune = [];
obj.extern = [];
end
function obj = render(obj)
%RENDER create user interface
%%% --- create widget panel --- %%%
obj.ui_panel = uipanel(...
'Parent', obj.ui_parent,...
'Title', 'Audio Stimuli',...
'TitlePosition', 'lefttop',...
'BorderType', 'line',...
'HighlightColor', obj.FOREGROUND_COLOR,...
'ForegroundColor', obj.FOREGROUND_COLOR,...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'normalized',...
'Position', [0, 0, 1, 1],...
'Units', 'pixels');
%%% --- create grid object --- %%%
obj.ui_grid = uiGridLayout(...
'Parent', obj.ui_panel,...
'VGrid', 7,...
'HGrid', 4,...
'VGap', obj.GRID_VGAP,...
'HGap', obj.GRID_HGAP);
%%% --- add UI elements --- %%%
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'duration [sec]',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 1, 'HIndex', 1:2));
obj.ui_grid.align(ui_text,...
'VIndex', 1,...
'HIndex', 1:2,...
'Anchor', 'east');
obj.ui_edit_duration = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%.2f', obj.duration),...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_duration,...
'VIndex', 1,...
'HIndex', 3:4,...
'Anchor', 'west');
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'freq.range [Hz]',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 2, 'HIndex', 1:2));
obj.ui_grid.align(ui_text,...
'VIndex', 2,...
'HIndex', 1:2,...
'Anchor', 'east');
obj.ui_edit_freqBase = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%d', obj.freqBase),...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_freqBase,...
'VIndex', 2,...
'HIndex', 3,...
'Anchor', 'west');
obj.ui_edit_freqThresh = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%d', obj.freqThresh),...
'Enable', 'off',...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_freqThresh,...
'VIndex', 2,...
'HIndex', 4,...
'Anchor', 'west');
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'chirp function',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 3, 'HIndex', 1:2));
obj.ui_grid.align(ui_text,...
'VIndex', 3,...
'HIndex', 1:2,...
'Anchor', 'east');
obj.ui_popup_chirp = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'popup',...
'String', obj.CHIRP_TYPE,...
'Value', 1,...
'Callback', @obj.fcnCallback_chirp,...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.POPUP_SIZE);
obj.ui_grid.align(obj.ui_popup_chirp,...
'VIndex', 3,...
'HIndex', 3:4,...
'Anchor', 'center');
obj.ui_checkbox_train = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'checkbox',...
'String', 'use train',...
'Value', 1,...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Callback', @obj.fcnCallback_checkTrain,...
'Units', 'pixels',...
'Position', obj.CHECKBOX_SIZE);
obj.ui_grid.align(obj.ui_checkbox_train,...
'VIndex', 4,...
'HIndex', 1:2,...
'Anchor', 'center');
obj.ui_checkbox_extern = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'checkbox',...
'String', 'use extern',...
'Value', 0,...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Callback', @obj.fcnCallback_checkExtern,...
'Units', 'pixels',...
'Position', obj.CHECKBOX_SIZE);
obj.ui_grid.align(obj.ui_checkbox_extern,...
'VIndex', 4,...
'HIndex', 3:4,...
'Anchor', 'center');
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'pulses',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 5, 'HIndex', 1));
obj.ui_grid.align(ui_text,...
'VIndex', 5,...
'HIndex', 1,...
'Anchor', 'east');
obj.ui_edit_pulses = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%d', obj.pulses),...
'Enable', 'on',...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_pulses,...
'VIndex', 5,...
'HIndex', 2,...
'Anchor', 'west');
ui_text = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'offset [sec]',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 6, 'HIndex', 1));
obj.ui_grid.align(ui_text,...
'VIndex', 6,...
'HIndex', 1,...
'Anchor', 'east');
obj.ui_edit_offset = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'edit',...
'String', sprintf('%.2f', obj.offset),...
'Enable', 'on',...
'Callback', @obj.fcnCallback_parse,...
'Units', 'pixels',...
'Position', obj.EDITBOX_SIZE);
obj.ui_grid.align(obj.ui_edit_offset,...
'VIndex', 6,...
'HIndex', 2,...
'Anchor', 'west');
obj.ui_pushbutton_load = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'pushbutton',...
'String', 'Load',...
'Enable', 'off',...
'Callback', @obj.fcnCallback_load,...
'Units', 'pixels',...
'Position', obj.PUSHBUTTON_SIZE);
obj.ui_grid.align(obj.ui_pushbutton_load,...
'VIndex', 5,...
'HIndex', 3:4,...
'Anchor', 'center');
obj.ui_text_extern = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'text',...
'String', 'load sound file',...
'Enable', 'off',...
'BackgroundColor', obj.BACKGROUND_COLOR,...
'Units', 'pixels',...
'Position', obj.ui_grid.getGrid('VIndex', 6, 'HIndex', 3:4));
obj.ui_grid.align(obj.ui_text_extern,...
'VIndex', 6,...
'HIndex', 3:4,...
'Anchor', 'center');
obj.ui_pushbutton_spectrum = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'pushbutton',...
'String', 'Spectrum',...
'Callback', @obj.fcnCallback_spectrum,...
'Units', 'pixels',...
'Position', obj.PUSHBUTTON_SIZE);
obj.ui_grid.align(obj.ui_pushbutton_spectrum,...
'VIndex', 7,...
'HIndex', 1:2,...
'Anchor', 'center');
obj.ui_pushbutton_play = uicontrol(...
'Parent', obj.ui_panel,...
'Style', 'pushbutton',...
'String', 'Play',...
'Callback', @obj.fcnCallback_play,...
'Units', 'pixels',...
'Position', obj.PUSHBUTTON_SIZE);
obj.ui_grid.align(obj.ui_pushbutton_play,...
'VIndex', 7,...
'HIndex', 3:4,...
'Anchor', 'center');
end
function obj = enable(obj, varstate)
%ENABLE toggle on/off enable state
if strcmp('on', varstate) || strcmp('off', varstate)
handles = [obj.ui_edit_duration,...
obj.ui_edit_freqBase,...
obj.ui_edit_freqThresh,...
obj.ui_edit_pulses,...
obj.ui_edit_offset,...
obj.ui_popup_chirp,...
obj.ui_checkbox_train,...
obj.ui_checkbox_extern,...
obj.ui_pushbutton_load,...
obj.ui_pushbutton_spectrum,...
obj.ui_pushbutton_play];
set(handles, 'Enable', varstate);
% check a chirp function condition
if get(obj.ui_popup_chirp, 'Value') == 1
set(obj.ui_edit_freqThresh, 'Enable', 'off');
end
end
end
end
%% --- callback methods --- %%
methods
function obj = fcnCallback_close(obj, ~, ~)
%FCNCALLBACK_CLOSE user request on destructor
obj.dispose();
end
function obj = fcnCallback_parse(obj, hSrc, ~)
%FCNCALLBACK_PARSE parse edit box
% parse edin box
varchar = get(hSrc, 'String');
varnum = str2double(regexp(varchar, '[0-9]+\.?[0-9]*', 'match'));
varnum(isempty(varnum)) = 0;
% assign to property
switch hSrc
case obj.ui_edit_duration
set(obj.ui_edit_duration, 'String', sprintf('%.2f', varnum));
obj.duration = varnum;
obj.compose();
case obj.ui_edit_freqBase
varnum = setRange(varnum, obj.MIN_FREQUENCY, obj.MAX_FREQUENCY);
set(obj.ui_edit_freqBase, 'String', sprintf('%d', varnum));
obj.freqBase = varnum;
obj.compose();
case obj.ui_edit_freqThresh
varnum = setRange(varnum, obj.MIN_FREQUENCY, obj.MAX_FREQUENCY);
set(obj.ui_edit_freqThresh, 'String', sprintf('%d', varnum));
obj.freqThresh = varnum;
obj.compose();
case obj.ui_edit_pulses
varnum = setRange(varnum, 1, obj.MAX_PULSES);
set(obj.ui_edit_pulses, 'String', sprintf('%d', varnum));
obj.pulses = varnum;
obj.compile();
case obj.ui_edit_offset
set(obj.ui_edit_offset, 'String', sprintf('%.2f', varnum));
obj.offset = varnum;
obj.compile();
end
end
function obj = fcnCallback_chirp(obj, ~, ~)
%FCNCALLBACK_CHIRP
if get(obj.ui_popup_chirp, 'Value') == 1
set(obj.ui_edit_freqThresh, 'Enable', 'off');
else
set(obj.ui_edit_freqThresh, 'Enable', 'on');
end
obj.compose();
end
function obj = fcnCallback_checkTrain(obj, ~, ~)
%FCNCALLBACK_CHECKTRAIN callback on check box click
if get(obj.ui_checkbox_train, 'Value') == 1
varstate = 'on';
else
varstate = 'off';
obj.pulses = 1;
obj.offset = 0;
set(obj.ui_edit_pulses, 'String', sprintf('%d', obj.pulses));
set(obj.ui_edit_offset, 'String', sprintf('%.2f', obj.offset));
end
set(obj.ui_edit_pulses, 'Enable', varstate);
set(obj.ui_edit_offset, 'Enable', varstate);
end
function obj = fcnCallback_checkExtern(obj, ~, ~)
%FCNCALLBACK_CHECKEXTERN toggle enable state of load button
% set Enable state
if get(obj.ui_checkbox_extern, 'Value') == 1
varstate = 'off';
varinvstate = 'on';
obj.load();
else
varstate = 'on';
varinvstate = 'off';
set(obj.ui_text_extern, 'String', 'load sound file');
obj.ui_grid.align(obj.ui_text_extern,...
'VIndex', 6,...
'HIndex', 3:4,...
'Anchor', 'center');
obj.compose();
end
% toggle Enable property
set(obj.ui_pushbutton_load, 'Enable', varinvstate);
set(obj.ui_text_extern, 'Enable', varinvstate);
set(obj.ui_edit_duration, 'Enable', varstate);
set(obj.ui_edit_freqBase, 'Enable', varstate);
set(obj.ui_edit_freqThresh, 'Enable', varstate);
set(obj.ui_popup_chirp, 'Enable', varstate);
% check if threshold frequency required
if strcmp(varstate, 'on') && get(obj.ui_popup_chirp, 'Value') == 1
set(obj.ui_edit_freqThresh, 'Enable', 'off');
end
end
function obj = fcnCallback_load(obj, ~, ~)
%FCNCALLBACK_LOAD on press load button
obj.load();
end
function obj = fcnCallback_spectrum(obj, ~, ~)
%FCNCALLBACK_SPECTRUM on press spectrum button
obj.spectrum();
end
function obj = fcnCallback_play(obj, ~, ~)
%FCNCALLBACK_PLAY on press play button
obj.play();
end
end
%% --- applied methods --- %%
methods
function obj = load(obj)
%LOAD add external file
[filename, pathname] = uigetfile({'*.wav'; '*.mp3'; '*.mp4'}, 'Pick an audio file');
if ~(isequal(filename, 0) || isequal(pathname, 0))
[~,filetag] = fileparts(filename);
obj.extern = filetag;
if length(filetag) > 16
filetag = filetag(1:16);
end
set(obj.ui_text_extern, 'String', filetag);
obj.ui_grid.align(obj.ui_text_extern,...
'VIndex', 6,...
'HIndex', 3:4,...
'Anchor', 'center');
[obj.ydata, samplingFreq] = audioread([pathname, filesep, filename]);
obj.ydata = obj.ydata(:, 1)';
obj.tdata = 0 : (1/samplingFreq) : length(obj.ydata)*(1/samplingFreq);
obj.tdata = obj.tdata(1:length(obj.ydata));
obj.compile();
end
end
function obj = spectrum(obj)
%SPECTRUM plots current tune spectrogram
% create current figure label
if get(obj.ui_checkbox_extern, 'Value') == 0
label = sprintf('Spectrogram: t=%.2f sec, freq=(%d, %d)[Hz], type=%s',...
obj.duration,...
obj.freqBase,...
obj.freqThresh,...
obj.CHIRP_TYPE{get(obj.ui_popup_chirp, 'Value')});
else
label = sprintf('Spectrogram: %s', obj.extern);
end
% add figure to handle array
obj.ui_spectrogram = cat(1, obj.ui_spectrogram,...
figure(...
'NumberTitle', 'off',...
'Name', label));
spectrogram(obj.tune,...
hamming(128),...
120,...
[],...
obj.SAMPLING_FREQ,...
'yaxis');
end
function obj = play(obj)
%PLAY plays current tune
play(obj.audio);
end
function obj = compose(obj)
%COMPOSE creates audio tune for stimuli
% time span
obj.tdata = 0 : 1/obj.SAMPLING_FREQ : obj.duration;
% generate audio signal
switch get(obj.ui_popup_chirp, 'Value')
case 1 % 'pure'
obj.ydata = chirp(obj.tdata, obj.freqBase, obj.duration, obj.freqBase, 'linear');
case 2 % 'linear'
obj.ydata = chirp(obj.tdata, obj.freqBase, obj.duration, obj.freqThresh, 'linear');
case 3 % 'exp'
obj.ydata = chirp(obj.tdata, obj.freqBase, obj.duration, obj.freqThresh, 'logarithmic');
end
% compile tune
obj.compile();
end
function obj = compile(obj)
%COMPILE compile tune in train and filter for fade in/out
% time offset
tOffset = 0: 1/obj.SAMPLING_FREQ : obj.offset;
yOffset = zeros(1, length(tOffset));
% fade in filter
idxFade = obj.tdata < obj.FADE_TIME;
fadeInFilter = cat(2,...
linspace(0,1,sum(idxFade)),...
ones(1,sum(~idxFade)));
% fade out filter
idxFade = obj.tdata > (obj.duration - obj.FADE_TIME);
fadeOutFilter = cat(2,...
ones(1,sum(~idxFade)),...
linspace(1,0,sum(idxFade)));
% fade fileter
fadeFilter = fadeInFilter .* fadeOutFilter;
obj.ydata = obj.ydata .* fadeFilter;
% generate audio train
if obj.pulses > 1
lastPoint = length(obj.ydata) * obj.pulses + length(yOffset) * (obj.pulses - 1);
obj.tune = repmat([obj.ydata,yOffset], 1, obj.pulses);
obj.tune = obj.tune(1:lastPoint);
else
obj.tune = obj.ydata;
end
% update audio object
obj.audio = audioplayer(obj.tune, obj.SAMPLING_FREQ);
end
end
end
%% --- Assistance --- %%
function varnum = setRange(varnum, freqMin, freqMax)
% round number
varnum = round(varnum);
% fix lower range
if varnum < freqMin
varnum = freqMin;
end
% fix upper range
if varnum > freqMax
varnum = freqMax;
end
end