classdef MTTMixedFeatureStober11 < MTTAudioFeature & handle
    % ---
    % This Class contains 
    % features are extracted 
    % as described in Slaney 08 - LEARNING A METRIC FOR MUSIC SIMILARITY
    %
    % The usual workflow for these features constist of three steps
    % 1. extract: extracts the basic single-file dependent features
    % 2. define_global_transform: calculates the global feature
    %       transformation parameters
    % 3. finalise: applies the common transformations to a specific feature
    % ---
    
    properties(Constant = true)
        
        % svn hook
        my_revision = str2double(substr('$Rev$',  5, -1));
    end

    properties
        % ---
        % Set default parameters
        % ---
        my_params = struct(...
            'stob_lowaudio', 1, ...
            'stob_highaudio', 1, ... % 
            'stob_tags', 1, ... %
            'stob_norm', 1 ...
            );
    end
    
    % ---
    % member functions
    % ---
    methods
        
        % ---
        % constructor: pointer to feature in database
        % ---
        function feature = MTTMixedFeatureStober11(varargin)

            feature = feature@MTTAudioFeature(varargin{:});

        end
        % ---
        % extract feature data from raw audio features
        % ---
        function data = extract(feature, clip)
            % ---
            % get features. this includes possible
            % local normalisations
            % ---
            
            global globalvars;
            global stobbase;
            
            if isempty(stobbase);
                stobbase = load('features_stober');
            end
            
            % ---
            % NOTE: we define feature sets which are included /
            % excluded according to the specified parameters
            % ---
            
            lowAudio = {'pitchMean', 'pitchSdev', 'timbreMean', 'timbreSdev'};
            
            % highAudio features more or less correspond to slaney 08 features
            hiAudio = {'energy','key','loudness','timeSignature',...
                'danceability', 'mode', 'tempo'};
            
            metadat = {'tags'};
            
            allowedFeat = {};
            
            % Select the features to keep
            if feature(1).my_params.stob_lowaudio
                allowedFeat = {allowedFeat{:},  lowAudio{:}};
            end
            if feature(1).my_params.stob_highaudio
                allowedFeat = {allowedFeat{:},  hiAudio{:}};
            end
            if feature(1).my_params.stob_tags
                allowedFeat = {allowedFeat{:},  metadat{:}};
            end
            
            % get the actual clip id
            idx = find(stobbase.clipIds == clip.id);
            
            % ---
            % NOTE: we just copy everything in a big matrix and then
            % normalise the data later
            % ---
            data.vector_info = {};
            data.stobraw = [];
            fields = fieldnames(stobbase);
            for i = 1:numel(fields)
                
                % skip clip ID field
                if strcmp(fields{i},'clipIds');
                    continue;
                end
                
                % skip unwanted features
                if isempty(strcellfind(allowedFeat, fields{i}))
                    continue;
                end
                
                % ---
                % TODO: special case for tag features, including
                % the tag names
                % ---
                
                % put field info into right position
                data.vector_info{numel(data.stobraw)+1} = fields{i};
                
                % add data to feature
                if size(stobbase.(fields{i}),1) == 1
                    data.stobraw(end+1) = stobbase.(fields{i})(idx);
                else
                    % concatenate vector
                    tmpdat = stobbase.(fields{i})(idx,:);
                    data.stobraw(end+1:end+numel(tmpdat)) = tmpdat;
                end
            end
            % padd further info struct
            data.vector_info(end+1:numel(data.stobraw)) =...
                cell(numel(data.stobraw) - numel(data.vector_info) , 1); 
            
            % ---
            % prepare field for final features
            % ---
            data.final.vector = [];
            data.final.vector_info = struct(); 
            data.final.dim = 0;
           
            % save info data
            data.info.type = 'MTTMixedFeatureStober11';
            data.info.owner_id = clip.id;
            data.info.creatorrev = feature.my_revision;
            
            % save parameters
            data.info.params = feature.my_params;
        end
        
        function define_global_transform(features)
        % calculate and set normalization factors from the group of 
        % input features. These features will be set for the full database
         
        final = zeros(numel(features(1).data.stobraw), numel(features));
        for i = 1:numel(features)
             if ~isempty(features(i).data.stobraw)
                final(:,i) = features(i).data.stobraw';
             end
        end
            
            if features(1).my_params.stob_norm
                if numel(features) == 1
                    error ('Insert feature array for this method, or set normalisation to 0');
                end

                % ---
                % here, we only need to define the post-normalisation
                % ---
                [final, pstd] = mapminmax(final,0,1);
                common.stobstats.post_norm = pstd;
                
                % ---
                % NOTE: whitening as in slaney?? 
                % Would make reading the
                % mahal matrices really hard
                % ---
                
                features(1).my_db.set_common(common);
                
            else
                
                features(1).my_db.set_common([1]);
            end
            
            % save the normalised features straight away!
            features.finalise(final);
        end
        
        
        function finalise(features, final)
        % applies a final transformation and
        % collects the information of this feature within a single vector
        % see info for types in specific dimensions
        % check if features have been finalised already

        % ---
        % set feature labelling
        % ---
        
        info = {};
                
        % ---
        % construct resulting feature vector out of features
        % ---
            if nargin == 2 && isempty(final)
                
                % the final vector etc already are set to zero;
                return;
                
            elseif nargin == 2 && (numel(features) == size(final, 2))
                % the features have already been preassembled
                
                for i = 1:numel(features)

                    % check for neccesary parameters
                    if isempty(features(i).my_db.commondb)

                        error('Define the global transformation first')
                        return;
                    end

                    features(i).data.final.vector = final(:,i);
                    features(i).data.final.dim = size(final,1);

                    % fill up info struct and append to feature
                    features(i).data.final.vector_info.labels = ...
                        features(i).data.vector_info;
                end
            else
                % ---
                % if features have been added after gettin gnormalisation
                % parameters, ther should be still an option to include
                % them
                % ---
               
                 for i = 1:numel(features)

                    % check for neccesary parameters
                    if isempty(features(i).my_db.commondb)

                        error('Define the global transformation first')
                        return;
                    end
                    
                    final = features(i).data.stobraw';
                    
                    if features(1).my_params.stob_norm == 1
                                
                        [final] = mapminmax('apply', final, features(1).common.stobstats.post_norm);
                    end
                    
                    features(i).data.final.vector = final;
                    features(i).data.final.dim = size(final,1);

                    % fill up info struct and append to feature
                    features(i).data.final.vector_info.labels = ...
                        features(i).data.vector_info;
                 end

            end
            
            % ---
            % TODO: Maybe delete more basic features again at this point?
            % ---
        end

        % ---
        % destructor: do we really want to remove this 
        % from the database? No, but 
        % TODO: create marker for unused objects in db, and a cleanup
        %  function
        % ---
        function delete(feature)
            
        end
    end
end