classdef theremine_ui < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        TheremineUIFigure            matlab.ui.Figure
        TheremineLabel               matlab.ui.control.Label
        biskHzEditFieldLabel         matlab.ui.control.Label
        MaxFrequency                 matlab.ui.control.NumericEditField
        LautstrkefaktorinKnobLabel   matlab.ui.control.Label
        VolumneKnob                  matlab.ui.control.Knob
        FrequenzHzvonEditFieldLabel  matlab.ui.control.Label
        MinFrequency                 matlab.ui.control.NumericEditField
        ResetPanel                   matlab.ui.container.Panel
        ResetPlotButton              matlab.ui.control.Button
        ResetAudioButton             matlab.ui.control.Button
        ResetAllButton               matlab.ui.control.Button
        ErrorLabel                   matlab.ui.control.Label
        Panel                        matlab.ui.container.Panel
        DateinameEditFieldLabel      matlab.ui.control.Label
        FileNameField                matlab.ui.control.EditField
        SaveButton                   matlab.ui.control.Button
        StartButton                  matlab.ui.control.Button
        LeftHandPlot                 matlab.ui.control.UIAxes
        SignalPlot                   matlab.ui.control.UIAxes
        ParameterPlot                matlab.ui.control.UIAxes
        RightHandPlot                matlab.ui.control.UIAxes
    end

properties (Access = private)
        % "Constants"
        % how many updates per second, determines the length of audio snippets
        const_frames_per_second;
        
        const_Fs;  % sampling rate in Hz
        const_te; % signal duration in seconds
        const_samples_per_frame;
        const_sample_range;

        % factor the last read frame is multiplied by
        const_fade_speed;
        
        % signal "generator"
        signal_generator;
        
        % dimensions to use for detecting number of hands in frame
        zero_hands;
        one_hand;
        two_hands;
        
        deviceWriter;
        
        % runtime variables
        P;
        count;
        done;
        
        frequency_pos;
        height_pos;
        
        offset_one;
        offset_two;
        
        signal_one;
        
        full_signal;
        complete_signal;
    end
    
    methods (Access = private)
        function [sound,offset] = get_theremin_sound_bit(app, x, y, offset)
            % finding ranges for x and y:
            %'x'
            %min(x)
            %max(x)
            %'y'
            %min(y)
            %max(y)
            % --> 50 < x < 270
            % --> 90 < y < 350
            
            volume = (y) / 1300;
            frequency = max(0.003, (x - 50) / 220) * 1000; % https://web.physics.ucsb.edu/~lecturedemonstrations/Composer/Pages/60.17.html # How it works
        
            base = app.signal_generator(frequency) + offset;
            
            offset = base(end);
            
            sound = sin(base) .* volume;
        end
        
        function updateUI(app)
            % todo
        end
    end
    

    % Callbacks that handle component events
    methods (Access = private)

        % Code that executes after component creation
        function startupFunction(app)
            % init to sane defaults
            %  "Constants"
            % how many updates per second, determines the length of audio snippets
            app.const_frames_per_second = 100;
            
            app.const_Fs = 44100;  % sampling rate in Hz
            app.const_te = 1/app.const_frames_per_second; % signal duration in seconds
            app.const_samples_per_frame = ceil(app.const_Fs * app.const_te);
            app.const_sample_range = 0:app.const_samples_per_frame-1;
    
            % factor the last read frame is multiplied by
            app.const_fade_speed = 0.975;
            
            % signal "generator"
            app.signal_generator = @(freq) (freq ./ app.const_Fs .* 2 .* pi .* app.const_sample_range);
            
            % dimensions to use for detecting number of hands in frame
            app.zero_hands = size(NaN(0,0));
            app.one_hand = size(NaN(1,1));
            app.two_hands = size(NaN(1,2));
            
            app.deviceWriter = audioDeviceWriter('SampleRate', app.const_Fs, 'SupportVariableSizeInput', true, 'BufferSize', 3 * app.const_samples_per_frame);
            
            % runtime variables
            app.P = NaN(1000000,3);
            app.count = 1;
            app.done = false;
            
            app.frequency_pos = 0;
            app.height_pos = 0;
            
            app.offset_one = 0;
            app.offset_two = 0;
            
            app.signal_one = sin(app.signal_generator(0));
            
            app.full_signal = app.signal_one;
            app.complete_signal = app.full_signal;
            
            %%%%%%%%%%%%%%%%%%%
            % init matleap by calling for first frame %
            %%%%%%%%%%%%%%%%%%%
            matleap_frame;
        end

        % Button pushed function: StartButton
        function OnStartButton(app, event)
            % disable button
            app.StartButton.set('Enable', 'off');
            
            % time gestures, used for program termination
            gesture_count = 0;
            
            while gesture_count < app.const_frames_per_second
                frame = matleap_frame;
                handCount = size(frame.hands);
                
                % slowly decrease volume
                app.height_pos = app.height_pos * app.const_fade_speed;
                
                if isequal(app.one_hand, handCount)        
                    %pos = frame.hands(1).palm.position;
                    pos = frame.hands(1).palm.stabilized_position;
                    
                    app.frequency_pos = pos(1);
                    app.height_pos = max(150, app.height_pos);
                    
                    app.P(app.count, 1:3) = pos;
                    app.count = app.count + 1;
                
                    if frame.gesture > 0
                        gesture_count = gesture_count + 1;
                    else
                        gesture_count = 0;
                    end
                elseif isequal(app.two_hands, handCount)
                    gesture_count = 0;
                    
                    %pos = frame.hands(1).palm.position;
                    pos = frame.hands(1).palm.stabilized_position;
                    
                    app.frequency_pos = pos(1);
                    %y_one = pos(2);
                    
                    app.P(app.count, 1:3) = pos;
                    app.count = app.count + 1;
                    
                    %pos = frame.hands(2).palm.position;
                    pos = frame.hands(2).palm.stabilized_position;
            
                    %x_two = pos(1);
                    app.height_pos = pos(2);
                else
                    % no hands, do nothing (or more than 2 (how?! :D))
                    gesture_count = 0;
                end
                
                plot(app.ParameterPlot, app.frequency_pos, app.height_pos);
                drawnow;
                
                % play current sound
                [app.signal_one, app.offset_one] = app.get_theremin_sound_bit(app.frequency_pos, app.height_pos, app.offset_one);
                %[signal_two, offset_two] = get_theremin_sound_bit(x_two, y_two, offset_two, signal);
                app.full_signal = app.signal_one;% + signal_two;
                
                buffer_under_flow = app.deviceWriter(app.full_signal(:)); 
                
                if buffer_under_flow ~= 0
                    disp("Buffer ran empty!");
                end
                
                app.complete_signal = [app.complete_signal app.full_signal];
            end
            
            % reenable
            app.StartButton.set('Enable', 'on');
        end

        % Close request function: TheremineUIFigure
        function CloseRequestFcn(app, event)
            release(app.deviceWriter)
            delete(app)
        end
    end

    % Component initialization
    methods (Access = private)

        % Create UIFigure and components
        function createComponents(app)

            % Create TheremineUIFigure and hide until all components are created
            app.TheremineUIFigure = uifigure('Visible', 'off');
            app.TheremineUIFigure.Position = [100 100 1030 765];
            app.TheremineUIFigure.Name = 'Theremine';
            app.TheremineUIFigure.CloseRequestFcn = createCallbackFcn(app, @CloseRequestFcn, true);

            % Create TheremineLabel
            app.TheremineLabel = uilabel(app.TheremineUIFigure);
            app.TheremineLabel.HorizontalAlignment = 'center';
            app.TheremineLabel.FontName = 'Times Newer Roman';
            app.TheremineLabel.FontSize = 33;
            app.TheremineLabel.FontWeight = 'bold';
            app.TheremineLabel.Position = [436 704 159 45];
            app.TheremineLabel.Text = 'Theremine';

            % Create biskHzEditFieldLabel
            app.biskHzEditFieldLabel = uilabel(app.TheremineUIFigure);
            app.biskHzEditFieldLabel.HorizontalAlignment = 'right';
            app.biskHzEditFieldLabel.Position = [196 119 49 22];
            app.biskHzEditFieldLabel.Text = 'bis (kHz)';

            % Create MaxFrequency
            app.MaxFrequency = uieditfield(app.TheremineUIFigure, 'numeric');
            app.MaxFrequency.Position = [255 119 43 22];

            % Create LautstrkefaktorinKnobLabel
            app.LautstrkefaktorinKnobLabel = uilabel(app.TheremineUIFigure);
            app.LautstrkefaktorinKnobLabel.HorizontalAlignment = 'center';
            app.LautstrkefaktorinKnobLabel.Position = [355 11 88 34];
            app.LautstrkefaktorinKnobLabel.Text = {'Lautstärkefaktor'; '(in %)'};

            % Create VolumneKnob
            app.VolumneKnob = uiknob(app.TheremineUIFigure, 'continuous');
            app.VolumneKnob.Position = [369 79 60 60];
            app.VolumneKnob.Value = 50;

            % Create FrequenzHzvonEditFieldLabel
            app.FrequenzHzvonEditFieldLabel = uilabel(app.TheremineUIFigure);
            app.FrequenzHzvonEditFieldLabel.HorizontalAlignment = 'right';
            app.FrequenzHzvonEditFieldLabel.Position = [50 119 91 22];
            app.FrequenzHzvonEditFieldLabel.Text = 'Frequenz (Hz) von';

            % Create MinFrequency
            app.MinFrequency = uieditfield(app.TheremineUIFigure, 'numeric');
            app.MinFrequency.Position = [147 119 43 22];

            % Create ResetPanel
            app.ResetPanel = uipanel(app.TheremineUIFigure);
            app.ResetPanel.TitlePosition = 'centertop';
            app.ResetPanel.Title = 'Zurücksetzten';
            app.ResetPanel.Position = [771 0 260 120];

            % Create ResetPlotButton
            app.ResetPlotButton = uibutton(app.ResetPanel, 'push');
            app.ResetPlotButton.Position = [13 47 100 25];
            app.ResetPlotButton.Text = 'Graphiken';

            % Create ResetAudioButton
            app.ResetAudioButton = uibutton(app.ResetPanel, 'push');
            app.ResetAudioButton.Position = [148 47 100 25];
            app.ResetAudioButton.Text = 'Audio';

            % Create ResetAllButton
            app.ResetAllButton = uibutton(app.ResetPanel, 'push');
            app.ResetAllButton.Position = [80 12 100 25];
            app.ResetAllButton.Text = 'Alles';

            % Create ErrorLabel
            app.ErrorLabel = uilabel(app.TheremineUIFigure);
            app.ErrorLabel.WordWrap = 'on';
            app.ErrorLabel.FontName = 'Dialog';
            app.ErrorLabel.FontSize = 15;
            app.ErrorLabel.FontWeight = 'bold';
            app.ErrorLabel.FontColor = [1 0 0];
            app.ErrorLabel.Position = [514 122 515 44];
            app.ErrorLabel.Text = '';

            % Create Panel
            app.Panel = uipanel(app.TheremineUIFigure);
            app.Panel.Position = [512 0 260 120];

            % Create DateinameEditFieldLabel
            app.DateinameEditFieldLabel = uilabel(app.Panel);
            app.DateinameEditFieldLabel.HorizontalAlignment = 'right';
            app.DateinameEditFieldLabel.Position = [39 75 60 22];
            app.DateinameEditFieldLabel.Text = 'Dateiname';

            % Create FileNameField
            app.FileNameField = uieditfield(app.Panel, 'text');
            app.FileNameField.Position = [114 75 100 22];
            app.FileNameField.Value = 'Theremin.wav';

            % Create SaveButton
            app.SaveButton = uibutton(app.Panel, 'push');
            app.SaveButton.Position = [77 24 100 26];
            app.SaveButton.Text = 'Speichern';

            % Create StartButton
            app.StartButton = uibutton(app.TheremineUIFigure, 'push');
            app.StartButton.ButtonPushedFcn = createCallbackFcn(app, @OnStartButton, true);
            app.StartButton.Position = [119 47 100 25];
            app.StartButton.Text = 'Start';

            % Create LeftHandPlot
            app.LeftHandPlot = uiaxes(app.TheremineUIFigure);
            title(app.LeftHandPlot, 'Linke Hand')
            xlabel(app.LeftHandPlot, 'X')
            ylabel(app.LeftHandPlot, 'Y')
            app.LeftHandPlot.XGrid = 'on';
            app.LeftHandPlot.YGrid = 'on';
            app.LeftHandPlot.Position = [41 466 300 225];

            % Create SignalPlot
            app.SignalPlot = uiaxes(app.TheremineUIFigure);
            title(app.SignalPlot, 'Signal')
            xlabel(app.SignalPlot, 't')
            ylabel(app.SignalPlot, 'V')
            app.SignalPlot.YLim = [-1 1];
            app.SignalPlot.XTick = [];
            app.SignalPlot.YTick = [-1 -0.5 0 0.5 1];
            app.SignalPlot.YTickLabel = {'-1'; '-0.5'; '0'; '0.5'; '1'};
            app.SignalPlot.XGrid = 'on';
            app.SignalPlot.YGrid = 'on';
            app.SignalPlot.Position = [22 173 987 283];

            % Create ParameterPlot
            app.ParameterPlot = uiaxes(app.TheremineUIFigure);
            title(app.ParameterPlot, 'Parameter')
            xlabel(app.ParameterPlot, 'Frequenz')
            ylabel(app.ParameterPlot, 'Lautstärke')
            app.ParameterPlot.XGrid = 'on';
            app.ParameterPlot.YGrid = 'on';
            app.ParameterPlot.Position = [366 466 300 225];

            % Create RightHandPlot
            app.RightHandPlot = uiaxes(app.TheremineUIFigure);
            title(app.RightHandPlot, 'Rechte Hand')
            xlabel(app.RightHandPlot, 'X')
            ylabel(app.RightHandPlot, 'Y')
            app.RightHandPlot.XGrid = 'on';
            app.RightHandPlot.YGrid = 'on';
            app.RightHandPlot.Position = [709 466 300 225];

            % Show the figure after all components are created
            app.TheremineUIFigure.Visible = 'on';
        end
    end

    % App creation and deletion
    methods (Access = public)

        % Construct app
        function app = theremine_ui

            % Create UIFigure and components
            createComponents(app)

            % Register the app with App Designer
            registerApp(app, app.TheremineUIFigure)

            % Execute the startup function
            runStartupFcn(app, @startupFunction)

            if nargout == 0
                clear app
            end
        end

        % Code that executes before app deletion
        function delete(app)

            % Delete UIFigure when app is deleted
            delete(app.TheremineUIFigure)
        end
    end
end