% Kontroller.m
% created by srinivas.gs
% contact me at kontroller@srinivas.gs
% Kontroller is at https://sourceforge.net/projects/daqkontroller/
% Kontroller is in the public domain
%
% ===How to use Kontroller===
% 1. run Kontroller from the command line using "Kontroller"
% 2. Kontroller will automatically detect NI hardware and determine
% channels to use
% 3. Click on "configure inputs". If you want to use an input channel, call
% it something in the text field. Specify the input range as a number in
% the smaller edit field
% (default is +/-10V)
% 4. Click on "configure outputs".If you want to use an output channel, call
% it something in the text field.
% 5. click on "configure controls". you should have vectors corresponding
% to the control singals you want to write in your workspace. choose which
% vector is written to which channel. Call the entire set with a certain
% name, and click "DONE". Close the window if youre done adding control
% paradigms. 
% 6. Set an appropriate sampling rate. default is 1000Hz. 
% 7. Choose the file you want to output data to. Data will be stored as a
% .mat file
% 8. Choose the input channels you want to look at, and press "start
% scopes" to look at your input live. 
% 9. If you want to run a trial, choose a control paradigm from the paradigm list
% and press "run"
% 
% === dependencies===
% 
% 1. MATLAB
% 2. The DAQ toolbox
% 3. strkat.m, which is available with this file.
%
% ===Known issues in this version===
%
% 1. only works with NI hardware
% 2. [fixed] but digital inputs not supported.
% 3. only works with an even number of channels
% 4. oscilloscope plots do not show any label
% 5. no way to rescale oscilloscope plots
% 6. no way to look at previous trials
% 7. [fixed]
% 8. manual control outputs do not "stick"--tend to osccilate
% 9. trial acquisition is handled using a foreground task--so it can't be
% stopped. future versions will use s.Startbackground;
% 10. only voltage acquisition is supported. no support for other devices,
% sound, or video, or instruments, or current measurements. 
% 11. default figure locations and sizes may not be optimal on all screens.
% 12. Manual control sliders don't work
% 13. no abort condition failsafe
% 14. cannot support multiple sampling rates for different channels
%
% === changelog ===
% v0.3.0--added metadata option.
% v0.3.1--cosmetic and UI changes
% v0.3.2--bug fix: trial numbers now reset when file is changed.
% v0.3.3--bug fixes
% v0.3.4--Kontroller now logs OutputChannelNames and sampling rate.
% v0.3.5--GUI for digital outputs
% v0.3.6--reorganised variables; internal housekeeping 
% v0.3.7--full support for digital outputs. needs MATLAB R2013a
% v0.3.8--run trial intialisation and data agglomeration split into two
% functions
% v0.3.9--run trial now uses a background task
%
% ===roadmap===
% - estimated time to completion of programme
% - data logging and trial running is a background task
% - support for continuous background control signals that are not part of
% control paradigms--e.g., control signals to a stepper motor.



function Kontroller()
VersionName= 'Kontroller v0.3.8';
%% persistent internal variables
 
% session handles
s=[]; % this is the session ID

% listeners
lh = []; % listener ID
lhMC = []; % listener for manual control

%  figure handles
f1 = []; f2=[]; f3 = []; f4 = [];
mef = []; % figure for metadata editor

% uicontrol handles
li = []; ri = []; lir = []; rir= [ ]; % analogue input handles
lo = []; ro = [];  % analogue outputs handles
dlo = []; dro = []; % digital outputs handles
MetadataTextControl= []; % handle for metadata control
MetadataTextDisplay = []; % handle for metadata display
ScopeHandles = [];
ControlHandles= [];
ParadigmNameUI = [];
MCoi = []; 
MCNumoi = []; % this is for manually entering a specific set point via a edit field

% internal control variables
MCOutputData = [];
metadatatext = []; % stores metadata in a cell array so that we can display it.
ScopeThese = [];
scopes_running = 0; % are the scopes running right now?
trial_running = 0; % is a trial running right now?

% internal data variables
thisdata = []; % stores data from current trial; needs to be combined with data
data = [];
scope_plot_data = [];
VarNames = [];
SaveToFile= [];
Trials = []; % this keeps track of how many trials have been done with each paradigm
metadata = [];  % stores metadata associated with the whole file. 

%% make the GUI
clc
wh = waitbar(0.1,'Kontroller is starting...');
f1 = figure('Position',[20 60 450 700],'Toolbar','none','Menubar','none','Name',VersionName,'NumberTitle','off','Resize','off','HandleVisibility','on','CloseRequestFcn',@QuitKontrollerCallback);
waitbar(0.2,wh,'Generating UI...'); figure(wh)
Konsole = uicontrol('Position',[15 600 425 90],'Style','text','String','Kontroller is starting...','FontName','Courier','HorizontalAlignment','left');
ConfigureInputChannelButton = uicontrol('Position',[15 540 140 50],'Style','pushbutton','Enable','off','String','Configure Inputs','FontSize',10,'Callback',@ConfigureInputChannels);
ConfigureOutputChannelButton = uicontrol('Position',[160 540 140 50],'Style','pushbutton','Enable','off','String','Configure Outputs','FontSize',10,'Callback',@ConfigureOutputChannels);
ConfigureControlSignalsButton = uicontrol('Position',[305 540 140 50],'Style','pushbutton','Enable','off','String','Configure Control','FontSize',10,'Callback',@ConfigureControlSignals);
InputChannelsPanel = uipanel('Title','Input Channels','FontSize',12,'units','pixels','pos',[15 330 240 200]);
PlotInputsList = {};
PlotOutputsList = {};
PlotInputs = uicontrol(InputChannelsPanel,'Position',[3 3 230 170],'Style','listbox','Min',0,'Max',2,'String',PlotInputsList,'FontSize',11);
OutputChannelsPanel = uipanel('Title','Output Channels','FontSize',12,'units','pixels','pos',[265 330 170 130]);
PlotOutputs = uicontrol(OutputChannelsPanel,'Position',[3 3 165 100],'Style','listbox','Min',0,'Max',2,'String',PlotOutputsList,'FontSize',11);

ControlParadigmList = {}; % stores a list of different control paradigm names. e.g., control, test, odour1, etc.
ParadigmPanel = uipanel('Title','Control Paradigms','FontSize',12,'units','pixels','pos',[15 30 170 130]);
ParadigmListDisplay = uicontrol(ParadigmPanel,'Position',[3 3 155 105],'Style','listbox','Enable','on','String',ControlParadigmList,'FontSize',12,'Min',0,'Max',1,'Callback',@ControlParadigmListCallback);
ControlParadigm = []; % stores the actual control signals for the different control paradigm
SaveControlParadigmsButton = uicontrol(f1,'Position',[15,165,160,30],'Style','pushbutton','String','Save control paradigms...','Callback',@SaveControlParadigms);


SamplingRateControl = uicontrol(f1,'Position',[133 5 50 20],'Style','edit','String','1000');
uicontrol(f1,'Position',[20 5 100 20],'Style','text','String','Sampling Rate');
RunTrialButton = uicontrol(f1,'Position',[320 5 110 50],'Enable','off','BackgroundColor',[0.8 0.9 0.8],'Style','pushbutton','String','RUN','FontWeight','bold','Callback',@RunTrial);

FileNameDisplay = uicontrol(f1,'Position',[200,60,230,50],'Style','text','String','No destination file selected');
FileNameSelect = uicontrol(f1,'Position',[200,5,100,50],'Style','pushbutton','String','Write to...','Callback',@SelectDestinationCallback);

AutomatePanel = uipanel('Title','Automate','FontSize',12,'units','pixels','pos',[205 120 230 200]);
uicontrol(AutomatePanel,'Style','text','FontSize',8,'String','Repeat all paradigms','Position',[1 120 100 50])
uicontrol(AutomatePanel,'Style','text','FontSize',8,'String','times','Position',[150 110 50 50])
RepeatNTimesControl = uicontrol(AutomatePanel,'Style','edit','FontSize',8,'String','1','Position',[110 140 30 30]);
RunProgramButton = uicontrol(AutomatePanel,'Position',[4 5 110 30],'Enable','off','Style','pushbutton','String','RUN PROGRAM','Callback',@RunProgram);
uicontrol(AutomatePanel,'Style','text','FontSize',8,'String','Do this between trials:','Position',[1 70 100 50])
InterTrialIntervalControl = uicontrol(AutomatePanel,'Style','edit','FontSize',8,'String','pause(20)','Position',[110 100 100 30]);
RandomizeControl = uicontrol(AutomatePanel,'Style','checkbox','FontSize',8,'Position',[5 50 20 20]);
uicontrol(AutomatePanel,'Style','text','FontSize',8,'String','Randomize','Position',[30 40 80 30])


ManualControlButton = uicontrol(f1,'Position',[10 230 170 30],'Enable','on','Style','pushbutton','String','Manual Control','Callback',@ManualControlCallback);
MetadatButton = uicontrol(f1,'Position',[10 270 170 30],'Enable','on','Style','pushbutton','String','Add Metadata...','Callback',@MetadataCallback);

waitbar(0.3,wh,'Generating global variables...'); figure(wh)
StartScopes = uicontrol(f1,'Position',[260 465 150 50],'Style','pushbutton','Enable','off','String','Start Scopes','FontSize',12,'Callback',@ScopeCallback);
scope_fig = figure('Position',[500 150 750 650],'Toolbar','none','Menubar','none','Name','Oscilloscope','NumberTitle','off','Resize','on','Visible','off','CloseRequestFcn',@QuitKontrollerCallback); hold on; 



%% figure out DAQ characteristics and initialise
waitbar(0.5,wh,'Scanning hardware...'); figure(wh)
d = daq.getDevices(); % this line takes a long time when you rin it for the first time...
figure(wh)

OutputChannels =  d.Subsystems(2).ChannelNames;
nOutputChannels = length(OutputChannels);
InputChannels =  d.Subsystems(1).ChannelNames;
nInputChannels = length(InputChannels);
InputChannelRanges = 10*ones(1,nInputChannels);
DigitalOutputChannels=d.Subsystems(3).ChannelNames;
nDigitalOutputChannels = length(DigitalOutputChannels);
UsedInputChannels = [];
InputChannelNames = {}; % this is the user defined names
UsedDigitalOutputChannels = [];
DigitalOutputChannelNames = {}; % this is the user defined names
UsedOutputChannels = [];
OutputChannelNames = {}; % this is the user defined names

waitbar(0.6,wh,'Checking for input config...'); figure(wh)
% load saved configs...inputs
if ~isempty(dir('TFAIConfig.mat'))
    disp('Loading saved input config files...')
    load('TFAIConfig.mat','UsedInputChannels','InputChannelNames')
    PlotInputsList = InputChannelNames(UsedInputChannels);
     set(PlotInputs,'String',PlotInputsList)
     if ~isempty(UsedInputChannels)
         set(StartScopes,'Enable','on')
     else 
         set(StartScopes,'Enable','off')
     end
     disp('DONE')
end
waitbar(0.7,wh,'Checking for output config...'); figure(wh)
% load saved configs..outputs
if ~isempty(dir('TFAOConfig.mat'))
    disp('Loading saved output config files...')
    load('TFAOConfig.mat','UsedOutputChannels','OutputChannelNames')
     if ~isempty(UsedOutputChannels)
         set(ConfigureControlSignalsButton,'Enable','on')
     else 
         set(ConfigureControlSignalsButton,'Enable','off')
     end
     % update PlotOutputsList
     PlotOutputsList = [OutputChannelNames(UsedOutputChannels) DigitalOutputChannelNames(UsedDigitalOutputChannels)];
     set(PlotOutputs,'String',PlotOutputsList);
     disp('DONE')
end
% load saved digital output configs
if ~isempty(dir('TFADigitalConfig.mat'))
    disp('Loading saved output config files...')
    load('TFADigitalConfig.mat','UsedDigitalOutputChannels','DigitalOutputChannelNames')
     if ~isempty(UsedOutputChannels)
         set(ConfigureControlSignalsButton,'Enable','on')
     else 
         set(ConfigureControlSignalsButton,'Enable','off')
     end
     % update PlotOutputsList
     PlotOutputsList = [OutputChannelNames(UsedOutputChannels) DigitalOutputChannelNames(UsedDigitalOutputChannels)];
     set(PlotOutputs,'String',PlotOutputsList);
     disp('DONE')
end
waitbar(1,wh,'DONE.'); figure(wh)
set(ConfigureInputChannelButton,'Enable','on')
set(ConfigureOutputChannelButton,'Enable','on')
set(Konsole,'String',strkat('Kontroller is ready to use. \n','Hardware detected: \n',d.Vendor.FullName,'-',d.Model))
close(wh)
set(scope_fig,'Visible','on')

%% configure inputs
    function [] =ConfigureInputChannels(eo,ed)
        % load saved configs      
        n = nInputChannels;
        Height = 600;
        f2 = figure('Position',[80 80 450 Height],'Toolbar','none','Menubar','none','resize','off','Name','Configure Analogue Input Channels','NumberTitle','off');
        a = axes; hold on
        set(a,'Visible','off');
        if floor(n/2)*2 == n
            % even n
            nspacing = Height/(n/2);
            % generate UIcontrol edit boxes
            for i = 1:n/2  % left side
                if ismember(i,UsedInputChannels)
                    li(i) = uicontrol(f2,'Position',[40 Height-i*nspacing 100 20],'Style', 'edit','String',InputChannelNames{i},'FontSize',12,'Callback',@InputConfigCallback);
                    lir(i) = uicontrol(f2,'Position',[7 Height-i*nspacing 25 20],'Style', 'edit','String',mat2str(InputChannelRanges(i)),'FontSize',10,'Callback',@InputConfigCallback);
                    
                else
                    li(i) = uicontrol(f2,'Position',[40 Height-i*nspacing 100 20],'Style', 'edit','FontSize',12,'Callback',@InputConfigCallback);
                    lir(i) = uicontrol(f2,'Position',[7 Height-i*nspacing 25 20],'Style', 'edit','String',mat2str(InputChannelRanges(i)),'FontSize',10,'Callback',@InputConfigCallback);
                    
                end
                uicontrol(f2,'Position',[160 Height-i*nspacing 50 20],'Style', 'text','String',InputChannels{i},'FontSize',12);
            end
            for i = 1:n/2  % right side
                if ismember(n/2+i,UsedInputChannels)
                    ri(i) = uicontrol(f2,'Position',[300 Height-i*nspacing 100 20],'Style', 'edit','String',InputChannelNames{n/2+i},'FontSize',12,'Callback',@InputConfigCallback);
                    rir(i) = uicontrol(f2,'Position',[407 Height-i*nspacing 25 20],'Style', 'edit','String',mat2str(InputChannelRanges(n/2+i)),'FontSize',10,'Callback',@InputConfigCallback);
                    
                else
                    ri(i) = uicontrol(f2,'Position',[300 Height-i*nspacing 100 20],'Style', 'edit','FontSize',12,'Callback',@InputConfigCallback);
                    rir(i) = uicontrol(f2,'Position',[407 Height-i*nspacing 25 20],'Style', 'edit','String',mat2str(InputChannelRanges(n/2+i)),'FontSize',10,'Callback',@InputConfigCallback);
                    
                end
                uicontrol(f2,'Position',[220 Height-i*nspacing 50 20],'Style', 'text','String',InputChannels{n/2+i},'FontSize',12);
            end
            
        else
            error('Odd number of channels, cannot handle this')
        end
    
    end

%% configure outputs
    function [] =ConfigureOutputChannels(eo,ed)
        % make the analogue outputs
        n = nOutputChannels;
        Height = 300;
        f3 = figure('Position',[50 150 450 Height],'Toolbar','none','Menubar','none','Name','Configure Analogue Output Channels','NumberTitle','off','CloseRequestFcn',@QuitConfigOutputsCallback);
        a = axes; hold on
        set(a,'Visible','off');
        if floor(n/2)*2 == n
            % even n
            nspacing = Height/(n/2+1);
            % generate UIcontrol edit boxes
            for i = 1:n/2  % left side
                if ismember(i,UsedOutputChannels)
                    lo(i) = uicontrol(f3,'Position',[40 Height-i*nspacing 100 20],'Style', 'edit','String',OutputChannelNames{i},'FontSize',12,'Callback',@OutputConfigCallback);
                else
                    lo(i) = uicontrol(f3,'Position',[40 Height-i*nspacing 100 20],'Style', 'edit','FontSize',12,'Callback',@OutputConfigCallback);
                end
                uicontrol(f3,'Position',[160 Height-i*nspacing 50 20],'Style', 'text','String',OutputChannels{i},'FontSize',12);
            end
            for i = 1:n/2  % right side
                if ismember(n/2+i,UsedOutputChannels)
                    
                    ro(i) = uicontrol(f3,'Position',[300 Height-i*nspacing 100 20],'Style', 'edit','String',OutputChannelNames{n/2+i},'FontSize',12,'Callback',@OutputConfigCallback);
                else
                    ro(i) = uicontrol(f3,'Position',[300 Height-i*nspacing 100 20],'Style', 'edit','FontSize',12,'Callback',@OutputConfigCallback);
                end
                uicontrol(f3,'Position',[220 Height-i*nspacing 50 20],'Style', 'text','String',OutputChannels{n/2+i},'FontSize',12);
            end
            
        else
            error('Odd number of channels, cannot handle this')
        end
        
        % make the digital outputs
        n = nDigitalOutputChannels;
        Height = 700;
        f4 = figure('Position',[550 150 550 Height],'Resize','off','Toolbar','none','Menubar','none','Name','Configure Digital Output Channels','NumberTitle','off','CloseRequestFcn',@QuitConfigOutputsCallback);
        a = axes; hold on
        set(a,'Visible','off');
        if floor(n/2)*2 == n
            % even n
            nspacing = Height/(n/2+1);
            % generate UIcontrol edit boxes
            for i = 1:n/2  % left side
                if ismember(i,UsedDigitalOutputChannels)
                    dlo(i) = uicontrol(f4,'Position',[40 Height-i*nspacing 100 20],'Style', 'edit','String',DigitalOutputChannelNames{i},'FontSize',10,'Callback',@OutputConfigCallback);
                else
                    dlo(i) = uicontrol(f4,'Position',[40 Height-i*nspacing 100 20],'Style', 'edit','FontSize',10,'Callback',@OutputConfigCallback);
                end
                uicontrol(f4,'Position',[160 Height-i*nspacing 100 20],'Style', 'text','String',DigitalOutputChannels{i},'FontSize',10);
            end
            for i = 1:n/2  % right side
                if ismember(n/2+i,UsedOutputChannels)
                    
                    dro(i) = uicontrol(f4,'Position',[390 Height-i*nspacing 100 20],'Style', 'edit','String',DigitalOutputChannelNames{n/2+i},'FontSize',10,'Callback',@OutputConfigCallback);
                else
                    dro(i) = uicontrol(f4,'Position',[390 Height-i*nspacing 100 20],'Style', 'edit','FontSize',10,'Callback',@OutputConfigCallback);
                end
                uicontrol(f4,'Position',[280 Height-i*nspacing 100 20],'Style', 'text','String',DigitalOutputChannels{n/2+i},'FontSize',10);
            end
            
        else
            error('Odd number of channels, cannot handle this')
        end
    
    end

%% manual control callback
function [] =ManualControlCallback(eo,ed)
        % stop scopes
        if scopes_running
            % stop scopes
            s.stop;
            delete(lh);
            % relabel scopes button
            set(StartScopes,'String','Start Scopes');
            scopes_running = 0;
        end
        % stop trial if necessary
        if trial_running
            error('Trial running, wait for trial to finish')
        end
        
        % make UI
        n = nOutputChannels;
        Height = 300;
        fMC = figure('Position',[60 50 650 Height],'Toolbar','none','Menubar','none','Name','Manual Control','NumberTitle','off','CloseRequestFcn',@QuitManualControlCallback);
        a = axes; hold on
        set(a,'Visible','off');
        if floor(n/2)*2 == n
            % even n
            nspacing = Height/(n/2+1);
            % generate UIcontrol edit boxes
            oi=1; % this is the index of each used ouput channel
            for i = 1:n/2  % left side
                if ismember(i,UsedOutputChannels)
                    MCoi(oi) = uicontrol(fMC,'Position',[90 Height-i*nspacing 100 20],'Style', 'slider','Min',0,'Max',5,'Value',0,'String',OutputChannelNames{i},'FontSize',16,'Callback',@ManualControlSliderCallback);
                    uicontrol(fMC,'Position',[220 Height-i*nspacing 50 20],'Style', 'text','String',OutputChannels{i},'FontSize',12);
                    MCNumoi(oi) = uicontrol(fMC,'Position',[20 Height-i*nspacing 60 20],'Style', 'edit','String','0','FontSize',12,'Callback',@ManualControlSliderCallback);
                    oi = oi +1; 
                end
            end
            for i = 1:n/2  % right side  
                if ismember(i+n/2,UsedOutputChannels)
                    MCoi(oi) = uicontrol(fMC,'Position',[390 Height-i*nspacing 100 20],'Style', 'slider','Min',0,'Max',5,'Value',0,'String',OutputChannelNames{n/2+i},'FontSize',16,'Callback',@ManualControlSliderCallback);
                    uicontrol(fMC,'Position',[320 Height-i*nspacing 50 20],'Style', 'text','String',OutputChannels{(n/2+i)},'FontSize',12);
                    MCNumoi(oi) = uicontrol(fMC,'Position',[520 Height-i*nspacing 60 20],'Style', 'edit','String','0','FontSize',12,'Callback',@ManualControlSliderCallback);
                    oi = oi +1;
                end
            end
            
        else
            % odd number of channels
            error('Odd number of channels. Cant handle this')
        end
        
        % configure session
        figure(scope_fig)
        w = 1000; % 1kHz sampling     
        s = daq.createSession('ni');
        s.IsContinuous = true;
        s.Rate = w; 
        
        % configure session inputs
        s.NotifyWhenDataAvailableExceeds = 100; % 10Hz 
        % update scope_plot_data
        scope_plot_data = NaN(length(get(PlotInputs,'Value')),5000); % 5 s of  data in each channel
        ScopeHandles = []; % axis handles for each sub plot in scope
        rows = ceil(length(get(PlotInputs,'Value'))/2);
        ScopeThese = get(PlotInputs,'Value');
        PlotNames = get(PlotInputs,'String'); 
        for i = 1:length(get(PlotInputs,'Value'))
            ScopeHandles(i) = subplot(2,rows,i);
            plotname=strcat(InputChannels{UsedInputChannels(i)},'-',InputChannelNames{UsedInputChannels(i)});
            ylabel(plotname)
            set(ScopeHandles(i),'XLim',[0 5000]), hold off
            s.addAnalogInputChannel('Dev1',InputChannels{UsedInputChannels(ScopeThese(i))}, 'Voltage'); % add channel
        end
        lh = s.addlistener('DataAvailable',@PlotCallback);
        
        % configure session outputs
        MCOutputData = zeros(length(UsedOutputChannels),500)'; 
        TheseChannels=OutputChannels(UsedOutputChannels);
        for i = 1:length(TheseChannels)
             s.addAnalogOutputChannel('Dev1',OutputChannels{UsedOutputChannels(i)}, 'Voltage');
        end
        s.queueOutputData(MCOutputData);
        %s.NotifyWhenScansQueuedBelow = 100; % this line causes random
        %stops; don't know why
        lhMC = s.addlistener('DataRequired',@(src,event) src.queueOutputData(MCOutputData));
        
        % start it
        s.ExternalTriggerTimeout = 100;
        s.startBackground();
        scopes_running= 1;
        
        

        
    
end

%% manual control slider and text entry callback
% bug here -- slider doesnt work becauyse its overwritten by the text edit
    function ManualControlSliderCallback(eo,ed)
        for i = 1:length(UsedOutputChannels)
            thisvalue = get(MCoi(i),'Value');
            MCOutputData(:,i) = thisvalue*ones(500,1);
        end
        % now check text entry fields. this overrides the slider
        for i = 1:length(UsedOutputChannels)
            thisvalue = str2double(get(MCNumoi(i),'String'));
            MCOutputData(:,i) = thisvalue*ones(500,1);
        end
        
        delete(lhMC)
        lhMC = s.addlistener('DataRequired',@(src,event) src.queueOutputData(MCOutputData));
        
        
        
    end

%% input config callback

    function [] = InputConfigCallback(eo,ed)
        UsedInputChannels = [];
        n = nInputChannels;
         % first scan left
         for i = 1:n/2
              if isempty(strmatch(get(li(i),'String'),InputChannels))
                  % use this channel
                  UsedInputChannels = [UsedInputChannels i];
                  InputChannelNames{i} = get(li(i),'String');
                  InputChannelRanges(i) = str2double(get(lir(i),'String'));
              end
         end
         % then scan right
         for i = 1:n/2
              if isempty(strmatch(get(ri(i),'String'),InputChannels))
                  % use this channel
                  UsedInputChannels = [UsedInputChannels n/2+i];
                  InputChannelNames{n/2+i} = get(ri(i),'String');
                  InputChannelRanges(n/2+i) = str2double(get(rir(i),'String'));
              end
         end
         
         % update the input channel list
         PlotInputsList = InputChannelNames(UsedInputChannels);
         set(PlotInputs,'String',PlotInputsList)
         if ~isempty(UsedInputChannels)
             set(StartScopes,'Enable','on')
             
         else 
             set(StartScopes,'Enable','off')
         end
         % save Input Channel Names for persisitent config
         save('TFAIConfig.mat','InputChannelNames','UsedInputChannels');
        
    end

%% output config callback
function [] = OutputConfigCallback(eo,ed)
    % configure analogue outputs
        UsedOutputChannels = [];
        n = nOutputChannels;
         % first scan left
         for i = 1:n/2
              if isempty(strmatch(get(lo(i),'String'),OutputChannels))
                  % use this channel
                  UsedOutputChannels = [UsedOutputChannels i];
                  OutputChannelNames{i} = get(lo(i),'String');
              end
         end
         % then scan right
         for i = 1:n/2
              if isempty(strmatch(get(ro(i),'String'),OutputChannels))
                  % use this channel
                  UsedOutputChannels = [UsedOutputChannels n/2+i];
                  OutputChannelNames{n/2+i} = get(ro(i),'String');
              end
         end
         
         % update the output channel control signal config
         if ~isempty(UsedOutputChannels)
             set(ConfigureControlSignalsButton,'Enable','on')
             
         else 
             set(ConfigureControlSignalsButton,'Enable','off')
         end
         % now update digital outputs
         UsedDigitalOutputChannels = [];
         n = nDigitalOutputChannels;
         % first scan left
         for i = 1:n/2
              if isempty(strmatch(get(dlo(i),'String'),DigitalOutputChannels))
                  % use this channel
                  UsedDigitalOutputChannels = [UsedDigitalOutputChannels i];
                  DigitalOutputChannelNames{i} = get(dlo(i),'String');
              end
         end
         % then scan right
         for i = 1:n/2
              if isempty(strmatch(get(dro(i),'String'),DigitalOutputChannels))
                  % use this channel
                  UsedDigitalOutputChannels = [UsedDigitalOutputChannels n/2+i];
                  DigitalOutputChannelNames{n/2+i} = get(dro(i),'String');
              end
         end
         
         % update the output channel control signal config
         if ~isempty(UsedOutputChannels) || ~isempty(UsedDigitalOutputChannels)
             set(ConfigureControlSignalsButton,'Enable','on')
             
         else 
             set(ConfigureControlSignalsButton,'Enable','off')
         end
         
         PlotOutputsList = [OutputChannelNames(UsedOutputChannels) DigitalOutputChannelNames(UsedDigitalOutputChannels)];
         set(PlotOutputs,'String',PlotOutputsList)
         % save Analogue Output Channel Names for persisitent config
         save('TFAOConfig.mat','OutputChannelNames','UsedOutputChannels');
         
         % save Digital Output Channel Names for persisitent config
         save('TFADigitalConfig.mat','DigitalOutputChannelNames','UsedDigitalOutputChannels');
        
end

%% oscilloscope callback
    function  [] = ScopeCallback(eo,ed)
        if isempty(PlotInputsList)
        else
            if scopes_running
                % stop scopes
                s.stop;
                delete(lh);
                % relabel scopes button
                set(StartScopes,'String','Start Scopes');
                scopes_running = 0;
            else
                % start scopes
                figure(scope_fig)
                w = 1000; % 1kHz sampling     
                % create session
                s = daq.createSession('ni');
                s.IsContinuous = true;
                s.NotifyWhenDataAvailableExceeds = w/10; % 10Hz
                % update scope_plot_data
                scope_plot_data = NaN(length(get(PlotInputs,'Value')),5000); % 5 s of  data in each channel
                ScopeHandles = []; % axis handles for each sub plot in scope
                rows = ceil(length(get(PlotInputs,'Value'))/2);
                ScopeThese = get(PlotInputs,'Value');
                for i = 1:length(get(PlotInputs,'Value'))
                    ScopeHandles(i) = subplot(2,rows,i);
                    set(ScopeHandles(i),'XLim',[0 5000]), hold off
                    title( strcat(InputChannels{UsedInputChannels(i)},' -- ',InputChannelNames{UsedInputChannels(i)}))
                    s.addAnalogInputChannel('Dev1',InputChannels{UsedInputChannels(ScopeThese(i))}, 'Voltage'); % add channel
                end
                
                s.Rate = w; 
                lh = s.addlistener('DataAvailable',@PlotCallback);
                
                % specify each channel's range
                for i = 1:length(s.Channels)
                    % figure out which channel it is
                    [a,~]=ind2sub(size(InputChannels), strmatch(s.Channels(i).ID, InputChannels, 'exact'));
                    s.Channels(i).Range = InputChannelRanges(a)*[-1 1];
                end
                
                % fix scope labels
                ScopeThese = 1:length(get(PlotInputs,'Value'));
                
                % relabel scopes button
                set(StartScopes,'String','Stop Scopes');
                s.startBackground();
                scopes_running = 1;
   
            end
       
        end   
    end

%% plot live data to scopes
    function [] = PlotCallback(src,event)
        
        for i = ScopeThese
            scope_plot_data(i,:)=[scope_plot_data(i,length(event.Data)+1:end) event.Data(:,i)'];
            plot(ScopeHandles(i),scope_plot_data(i,:));
            
        end
    end

%% configure control signals
    function [] = ConfigureControlSignals(eo,ed)
        no = length(UsedOutputChannels) + length(UsedDigitalOutputChannels);
        Height = 100+no*100;
        % figure out the variables in the workspace that you can use. 
        % we require them to be a 1D vector. that's it. 
        var=evalin('base','whos');
        badvar=  [];
        for i  =1:length(var)
            if  ~((length(var(i).size)==2) || (min(var(i).size) == 1))
                badvar = [badvar i];
            end
        end
        
        var(badvar) = []; clear badvar
        
        % make the gui
        fcs= figure('Position',[200 200 450 Height],'Toolbar','none','Menubar','none','Name','Select Control Signals','NumberTitle','off','Resize','off');
        ControlHandles = [];
        if length(var) >= no
            % assemble names into a cell array
            VarNames = {};
            for i = 1:length(var)
                VarNames{i} = var(i).name;
            end
            
            
            % get name of control paradigm
            ParadigmNameUI=uicontrol(fcs,'Position',[(450-340)/2 Height-30 340 24],'Style', 'edit','String','Enter Name of Control Paradigm','FontSize',12);
            
            
            for i = 1:length(UsedOutputChannels)
                ControlHandles(i) = uicontrol(fcs,'Position',[150 10+i*100 150 50],'Style','popupmenu','Enable','on','String',VarNames,'FontSize',12);
                uicontrol(fcs,'Position',[30 30+i*100 100 30],'Style','text','String',OutputChannels{UsedOutputChannels(i)},'FontSize',12);
                uicontrol(fcs,'Position',[320 30+i*100 100 30],'Style','text','String',OutputChannelNames{UsedOutputChannels(i)},'FontSize',12);

            end
            ti=1;
            for i = length(UsedOutputChannels)+1:no
                ControlHandles(i) = uicontrol(fcs,'Position',[150 10+i*100 150 50],'Style','popupmenu','Enable','on','String',VarNames,'FontSize',12);
                uicontrol(fcs,'Position',[30 30+i*100 100 30],'Style','text','String',DigitalOutputChannels{UsedDigitalOutputChannels(ti)},'FontSize',12);
                uicontrol(fcs,'Position',[320 30+i*100 100 30],'Style','text','String',DigitalOutputChannelNames{UsedDigitalOutputChannels(ti)},'FontSize',12);
                t=ti+1;
            end
            clear ti
            % button to save paradigm
            uicontrol(fcs,'Position',[370 30 60 30],'Style','pushbutton','String','+Add','FontSize',12,'Callback',@ConfigureControlCallback);

        else
            % tell the user they don't enough variables to configure controls
            uicontrol(fcs,'Position',[25 70 400 200],'Style','text','String','To manually configure a control paradigm, you must have at least as many vectors in your MATLAB workspace as you have analogue outputs. This is not the case. Either close this and create some, or load a previously saved control paradigm from file. ','FontSize',12);
        
       end
        % button for loading saved control paradigms
        uicontrol(fcs,'Position',[10 30 260 30],'Style','pushbutton','String','Load saved paradigms...','FontSize',12,'Callback',@LoadSavedParadigms);
        
        
        
    end

%% configure control callback
    function [] = ConfigureControlCallback(eo,ed)
        no = length(UsedOutputChannels) + length(UsedDigitalOutputChannels);
        % assume everything is OK, and make a paradigm
        ControlParadigm(length(ControlParadigm)+1).Name= get(ParadigmNameUI,'String');
        thisp = length(ControlParadigm);
        % and now fill in the analogue control signals
        for i = 1:length(UsedOutputChannels);
            ControlParadigm(thisp).Outputs(i,:)=evalin('base',cell2mat(VarNames(get(ControlHandles(i),'Value'))));
        end
        % now fill in the digital control signals
        ti=1;
        for i = length(UsedOutputChannels):no
            ControlParadigm(thisp).Outputs(i,:)=evalin('base',cell2mat(VarNames(get(ControlHandles(i),'Value'))));
            ti=ti+1;
        end
        
        % update the paradigm list
        ControlParadigmList = [ControlParadigmList get(ParadigmNameUI,'String')];
        set(ParadigmListDisplay,'String',ControlParadigmList)
        
        % update Trial count
        Trials = zeros(1,length(ControlParadigmList));
        set(Konsole,'String','Controls have been configured. ')
    end

%% select destintion callback
    function [] = SelectDestinationCallback(eo,ed)
        temp=strcat(datestr(now,'yyyy_mm_dd'),'_XXs_TFA_GINXXX_m1fly_odour_300.mat');
        SaveToFile=uiputfile(strcat('C:\data\',temp));
        % activate the run buttons
        set(RunTrialButton,'enable','on','BackgroundColor',[0.1 0.9 0.1]);
        set(RunProgramButton,'enable','on');
        % update display
        set(FileNameDisplay,'String',SaveToFile);
        % reset Trial count
        Trials = zeros(1,length(ControlParadigmList)); 
        data = []; % clears the data, so that new data is written to the new file
         
    end

%% run programmme
    function [] = RunProgram(eo,ed)
        % get inter-trial function
        iti = (get(InterTrialIntervalControl,'String'));
        ntrials= str2num(get(RepeatNTimesControl,'String'));
        %determine sequence
        np = 1:length(get(ParadigmListDisplay,'String'));
        sequence = repmat(np,1,ntrials);
        if get(RandomizeControl,'Value')         
            sequence = sequence(randperm(length(sequence),length(sequence)));
        else
            % do it normally  
        end
        
        for i = sequence
            set(ParadigmListDisplay,'Value',i);
            set(Konsole,'String','Running inter-trial function....')
            eval(iti)
            RunTrial;
        end
        set(Konsole,'String','Programme has finished running.')
    end

%% control paradigm list callback
    function [] = ControlParadigmListCallback(eo,ed)
        if Trials(get(ParadigmListDisplay,'Value'))
            showthis = strkat(mat2str(Trials(get(ParadigmListDisplay,'Value'))),'  trials recorded on selected Paradigm');
            set(Konsole,'String',showthis)
        else
            % no trials on this paradigm
            set(Konsole,'String','No trials have been recorded on this paradigm.')
        end
    end

%% run trial
    function [] = RunTrial(eo,ed)        
        if scopes_running
            % stop scopes
            s.stop;
            delete(lh);
            % relabel scopes button
            set(StartScopes,'String','Start Scopes');
            scopes_running = 0;
        end
            
        set(RunTrialButton,'Enable','off')
        % figure out which pradigm to run
        ThisParadigm= (get(ParadigmListDisplay,'Value'));
        % figure out trial no
        if ~length(data)
            % no data at all
            Trials(ThisParadigm) = 1;
            set(Konsole,'String',strkat('Running Trial: \n','Paradigm: \t \t  ',ControlParadigmList{ThisParadigm},'\n Trial: \t \t ','1'))
        else
            if length(data) < ThisParadigm
            
                % first trial in this paradigm
                Trials(ThisParadigm) = 1;
                set(Konsole,'String',strkat('Running Trial: \n','Paradigm: \t \t  ',ControlParadigmList{ThisParadigm},'\n Trial:\t \t ',mat2str(1)))
       
            else
                sz = [];
                eval(strcat('sz=size(data(ThisParadigm).',InputChannelNames{UsedInputChannels(1)},');'));
                Trials(ThisParadigm) = sz(1)+1;
                set(Konsole,'String',strkat('Running Trial: \n','Paradigm: \t \t  ',ControlParadigmList{ThisParadigm},'\n Trial: \t \t ',mat2str(sz(1)+1)))
       
                
            end
            
        end
        
        w=str2num(get(SamplingRateControl,'String'));
        if isempty(w)
            error('Sampling Rate not defined!')
        end
        T= length(ControlParadigm(ThisParadigm).Outputs)/w; % duration of trial, for this trial
        % create session
        s = daq.createSession('ni');
        s.DurationInSeconds = T;
        s.NotifyWhenDataAvailableExceeds = w/10; % 10Hz
        s.Rate = w; % sampling rate, user defined.
         
        % show the traces as we acquire them on the scope
        figure(scope_fig)
        
        % update scope_plot_data
        ScopeHandles = []; % axis handles for each sub plot in scope
        rows = ceil(length(get(PlotInputs,'Value'))/2);
        ScopeThese = get(PlotInputs,'Value');
        scope_plot_data = NaN(length(UsedInputChannels),T*w);
        
        ti = 1;
        for i = ScopeThese
            ScopeHandles(i) = subplot(2,rows,ti); ti = ti+1;
            set(ScopeHandles(i),'XLim',[0 T*w]), hold off
        end
         
        % add the analogue input channels
        TheseChannels=InputChannels(UsedInputChannels);
        for i = 1:length(TheseChannels)
            s.addAnalogInputChannel('Dev1',InputChannels{UsedInputChannels(i)}, 'Voltage');
        end
        % add the analogue output channels
        TheseChannels=OutputChannels(UsedOutputChannels);
        for i = 1:length(TheseChannels)
             s.addAnalogOutputChannel('Dev1',OutputChannels{UsedOutputChannels(i)}, 'Voltage');
        end
        % add the digital output channels
        TheseChannels=DigitalOutputChannels(UsedDigitalOutputChannels);
        for i = 1:length(TheseChannels)
             s.addDigitalChannel('Dev1',DigitalOutputChannels{UsedDigitalOutputChannels(i)}, 'OutputOnly');
        end
        % configure listener to plot data on the scopes 
        lh = s.addlistener('DataAvailable',@PlotCallback);
        % queue data
        
        s.queueOutputData(ControlParadigm(ThisParadigm).Outputs');
            
       
        % read and write
        thisdata=s.startForeground();
        delete(lh)
        thisdata=thisdata';
        ProcessTrialData;
    end
%% process data == this function is called when the trial finishes running
    function [] = ProcessTrialData(eo,ed)
        ThisParadigm= (get(ParadigmListDisplay,'Value'));
        % combine data and label correctly
        % check if data exists
        if isempty(data)
            % create it          
            for i = 1:length(UsedInputChannels)
                eval( strcat('data(ThisParadigm).',InputChannelNames{UsedInputChannels(i)},'=thisdata(',mat2str(i),',:);'));
            end
        else
            % some data already exists, need to append
            % find the correct pradigm
            if length(data) < ThisParadigm
                % first trial in this paradigm
                for i = 1:length(UsedInputChannels)
                    eval(strcat('data(ThisParadigm).',InputChannelNames{UsedInputChannels(i)},'=[];'))
                end
            end

            for i = 1:length(UsedInputChannels)

                eval( strcat('data(ThisParadigm).',InputChannelNames{UsedInputChannels(i)},'=vertcat(data(ThisParadigm).',InputChannelNames{UsedInputChannels(i)},',thisdata(',mat2str(i),',:));'))
            end

        end
            
        % save data to file
        SamplingRate= str2double(get(SamplingRateControl,'String'));
        save(strcat('C:\data\',SaveToFile),'data','ControlParadigm','metadata','OutputChannelNames','SamplingRate');       
        set(RunTrialButton,'Enable','on')      
        set(Konsole,'String',strkat('Trial ',mat2str(Trials(ThisParadigm)),'/Paradigm ',mat2str(ThisParadigm),' completed.'));

    end

%% save control paradigms
    function [] = SaveControlParadigms(eo,ed)
        temp=strcat(datestr(now,'yyyy_mm_dd'),'_Kontroller_paradigm_.mat');
        ControlParadigmSaveToFile=uiputfile(temp);
        save(ControlParadigmSaveToFile,'ControlParadigm');
    end

%% load saved paradigms
    function [] = LoadSavedParadigms(eo,ed)
        [FileName,PathName] = uigetfile('*_Kontroller_paradigm*');
        temp=load(strcat(PathName,FileName));
        ControlParadigm=temp.ControlParadigm;
        clear temp
        % now update the list
        ControlParadigmList = {};
        for i = 1:length(ControlParadigm)
            ControlParadigmList = [ControlParadigmList ControlParadigm(i).Name];
        end
        set(ParadigmListDisplay,'String',ControlParadigmList)
        
    end

%% metadata callback
    function [] = MetadataCallback(eo,ed)
        % open the editor
        mef = figure('Position',[60 50 450 400],'Toolbar','none','Menubar','none','Name','Metadata Editor','NumberTitle','off','Resize','off');
        uicontrol(mef,'Style','Text','String','Add or modify metadata using standard MATLAB syntax, one variable at a time, below:','Position',[5 340 440 50],'HorizontalAlignment','left')
        MetadataTextControl = uicontrol(mef,'Style', 'edit', 'String','','Position',[5 285 440 40],'HorizontalAlignment','left','Callback',@AddMetadata);
        MetadataTextDisplay = uicontrol(mef,'Style','Text','String',metadatatext,'Position',[5 5 440 270]);
        
    end

%% metadata editor callback
    function [] = AddMetadata(eo,ed)
        % evaluate it
        eval(strcat('metadata.',get(MetadataTextControl,'String')));
        % rebuild display cell string
        metadatatext = [];
        fn = fieldnames(metadata);
        for i = 1:length(fn)
            metadatatext{i} = strcat(fn{i},' : ',mat2str(getfield(metadata,fn{i})));
        end
        set(MetadataTextDisplay,'String',metadatatext);
        set(MetadataTextControl,'String','');
        
    end

%% clean up when quitting Kontroller
    function [] = QuitKontrollerCallback(eo,ed)
       selection = questdlg('Are you sure you want to quit Kontroller?','Confirm quit.','Yes','No','Yes'); 
       switch selection, 
          case 'Yes',
              try
                delete(scope_fig)
              end
              try
                  delete(f1)
              end
          case 'No'
          return 
       end
    end

%% clean up when quitting Manual Control
    function [] = QuitManualControlCallback(eo,ed)
        % stop session
        try
            s.stop;
            delete(lh)
            delete(lhMC)
        catch
        end
        try
            delete(mef)
        end
        delete(gcf)
        scopes_running=0;

    end

%% on closing output config wiindows
    function  [] = QuitConfigOutputsCallback(eo,ed)
        % close both windows together
        try
            delete(f3);
        end
        try
            delete(f4);
        end
    end

end