Code covered by the BSD License  

Highlights from
MIRtoolbox

image thumbnail
from MIRtoolbox by Olivier Lartillot
An innovative environment, on top of Matlab, for music and audio analysis

mirchromagram.m
function varargout = mirchromagram(orig,varargin)
%   c = mirchromagram(x) computes the chromagram, or distribution of energy 
%       along pitches, of the audio signal x.
%       (x can be the name of an audio file as well, or a spectrum, ...)
%   Optional argument:
%       c = mirchromagram(...,'Tuning',t): specifies the central frequency
%           (in Hz.) associated to  chroma C.
%               Default value, t = 261.6256 Hz
%       c = mirchromagram(...,'Wrap',w): specifies whether the chromagram is
%           wrapped or not.
%           w = 1: groups all the pitches belonging to same pitch classes
%               (default value)
%           w = 0: pitches are considered as absolute values.
%       c = mirchromagram(...,'Frame',l,h) orders a frame decomposition of window
%           length l (in seconds) and hop factor h, expressed relatively to
%           the window length. For instance h = 1 indicates no overlap.
%           Default values: l = .2 seconds and h = .05
%       c = mirchromagram(...,'Center'): centers the result.
%       c = mirchromagram(...,'Normal',n): performs a n-norm of the
%           resulting chromagram. Toggled off if n = 0
%               Default value: n = Inf (corresponding to a normalization by
%                   the maximum value).
%       c = mirchromagram(...,'Pitch',p): specifies how to label chromas in
%           the figures.
%               p = 1: chromas are labeled using pitch names (default)
%                   alternative syntax: chromagram(...,'Pitch')
%               p = 0: chromas are labeled using MIDI pitch numbers
%       c = mirchromagram(...,'Triangle'): weight the contribution of each
%           frequency with respect to the distance with the actual
%           frequency of the corresponding chroma.
%       c = mirchromagram(...,'Weight',o): specifies the relative radius of
%           the weighting window, with respect to the distance between
%           frequencies of successive chromas.
%           o = 1: each window begins at the centers of the previous one.
%           o = .5: each window begins at the end of the previous one.
%               (default value)
%       mirchromagram(...,'Min',mi) indicates the lowest frequency taken into
%           consideration in the spectrum computation, expressed in Hz.
%           Default value: 100 Hz. (Gomez, 2006)
%       mirchromagram(...,'Max',ma) indicates the highest frequency taken into
%           consideration in the spectrum computation, expressed in Hz.
%           This upper limit is further shifted to a highest value until
%           the frequency range covers an exact multiple of octaves.
%           Default value: 5000 Hz. (Gomez, 2006)
%       mirchromagram(...,'Res',r) indicates the resolution of the
%           chromagram in number of bins per octave.
%               Default value, r = 12.
%
% Gmez, E. (2006). Tonal description of music audio signal. Phd thesis, 
%   Universitat Pompeu Fabra, Barcelona .

        cen.key = 'Center';
        cen.type = 'Boolean';
        cen.default = 0;
    option.cen = cen;
    
        nor.key = {'Normal','Norm'};
        nor.type = 'Integer';
        nor.default = Inf;
    option.nor = nor;
    
        wth.key = 'Weight';
        wth.type = 'Integer';
        wth.default = .5;
    option.wth = wth;
    
        tri.key = 'Triangle';
        tri.type = 'Boolean';
        tri.default = 0;
    option.tri = tri;
    
        wrp.key = 'Wrap';
        wrp.type = 'Boolean';
        wrp.default = 1;
    option.wrp = wrp;
    
        plabel.key = 'Pitch';
        plabel.type = 'Boolean';
        plabel.default = 1;
    option.plabel = plabel;
    
        thr.key = {'Threshold','dB'};
        thr.type = 'Integer';
        thr.default = 20;
    option.thr = thr;
    
        min.key = 'Min';
        min.type = 'Integer';
        min.default = 100;
    option.min = min;
    
        max.key = 'Max';
        max.type = 'Integer';
        max.default = 5000;
    option.max = max;

        res.key = 'Res';
        res.type = 'Integer';
        res.default = 12;
    option.res = res;

        origin.key = 'Tuning';
        origin.type = 'Integer';
        origin.default = 261.6256;
    option.origin = origin;
   
specif.option = option;
specif.defaultframelength = .2;
specif.defaultframehop = .05;

varargout = mirfunction(@mirchromagram,orig,varargin,nargout,specif,@init,@main);


function [x type] = init(x,option)
if isamir(x,'mirtemporal') || isamir(x,'mirspectrum')
    freqmin = option.min;
    freqmax = freqmin*2;
    while freqmax < option.max
        freqmax = freqmax*2;
    end
    %freqres = freqmin*(2.^(1/option.res)-1);
        % Minimal frequency resolution should correspond to frequency range
        %   between the first two bins of the chromagram 
        
    x = mirspectrum(x,'dB',option.thr,'Min',freqmin,'Max',freqmax,...
                      'NormalInput','MinRes',option.res,'OctaveRatio',.85);
                  %freqres*.5,...
                  %    'WarningRes',freqres);
end
type = 'mirchromagram';


function c = main(orig,option,postoption)
if iscell(orig)
    orig = orig{1};
end
if option.res == 12
    chromascale = {'C','C#','D','D#','E','F','F#','G','G#','A','A#','B'};
else
    chromascale = 1:option.res;
    option.plabel = 0;
end
if isa(orig,'mirchromagram')
    c = modif(orig,option,chromascale);
else
    c.plabel = 1;
    c.wrap = 0;
    c.chromaclass = {};
    c.chromafreq = {};
    c.register = {};
    c = class(c,'mirchromagram',mirdata(orig));
    c = purgedata(c);
    c = set(c,'Title','Chromagram','Ord','magnitude','Interpolable',0);
    if option.wrp
        c = set(c,'Abs','chroma class');
    else
        c = set(c,'Abs','chroma');
    end
    m = get(orig,'Magnitude');
    f = get(orig,'Frequency');
    %disp('Computing chromagram...')
    fs = get(orig,'Sampling');
    n = cell(1,length(m));  % The final structured list of magnitudes.
    cc = cell(1,length(m));  % The final structured list of chroma classes.
    o = cell(1,length(m));  % The final structured list of octave registers.
    p = cell(1,length(m));  % The final structured list of chromas.
    cf = cell(1,length(m));  % The final structured list of central frequencies related to chromas.
    for i = 1:length(m)
        mi = m{i};
        fi = f{i};
        if not(iscell(mi))
            mi = {mi};
            fi = {fi};
        end
        ni = cell(1,length(mi));    % The list of magnitudes.
        ci = cell(1,length(mi));    % The list of chroma classes.
        oi = cell(1,length(mi));    % The list of octave registers.
        pi = cell(1,length(mi));    % The list of absolute chromas.
        cfi = cell(1,length(mi));    % The central frequency of each chroma.
        for j = 1:length(mi)
            mj = mi{j};
            fj = fi{j};
                        
            % Let's remove the frequencies exceeding the last whole octave.
            minfj = min(min(min(fj)));
            maxfj = max(max(max(fj)));
            maxfj = minfj*2^(floor(log2(maxfj/minfj)));
            fz = find(fj(:,1,1,1) > maxfj);
            mj(fz,:,:,:) = [];      
            fj(fz,:,:,:) = [];
            
            [s1 s2 s3] = size(mj);
            
            cj = freq2chro(fj,option.res,option.origin);
            if not(ismember(min(cj)+1,cj))
                warning('WARNING IN MIRCHROMAGRAM: Frequency resolution of the spectrum is too low.');    
                display('The conversion of low frequencies into chromas may be incorrect.');
            end
            ccj = min(min(min(cj))):max(max(max(cj)));
            sc = length(ccj);   % The size of range of absolute chromas.
            mat = zeros(s1,sc);
            fc = chro2freq(ccj,option.res,option.origin);   % The absolute chromas in Hz.
            fl = chro2freq(ccj-1,option.res,option.origin); % Each previous chromas in Hz.
            fr = chro2freq(ccj+1,option.res,option.origin); % Each related next chromas in Hz.
            for k = 1:sc
                rad = find(and(fj(:,1) > fc(k)-option.wth*(fc(k)-fl(k)),...
                               fj(:,1) < fc(k)-option.wth*(fc(k)-fr(k))));
                if option.tri
                    dist = fc(k) - fj(:,1,1,1);
                    rad1 = dist/(fc(k) - fl(k))/option.wth;
                    rad2 = dist/(fc(k) - fr(k))/option.wth;
                    ndist = max(rad1,rad2);
                    mat(:,k) = max(min(1-ndist,1),0)/length(rad);
                else
                    mat(rad,k) = ones(length(rad),1)/length(rad);
                end
                if k ==1 || k == sc
                    mat(:,k) = mat(:,k)/2;
                end
            end
            nj = zeros(sc,s2,s3);
            for k = 1:s2
                for l = 1:s3
                    nj(:,k,l) = (mj(:,k,l)'*mat)';
                end
            end
            cj = mod(ccj',option.res);
            oi{j} = floor(ccj/option.res)+4;
            if option.plabel
                pj = strcat(chromascale(cj+1)',num2str(oi{j}'));
            else
                pj = ccj'+60;
            end
            ci{j} = repmat(cj,[1,s2,s3]);
            pi{j} = repmat(pj,[1,s2,s3]);
            ni{j} = nj;
            cfi{j} = fc;
        end
        n{i} = ni;
        cc{i} = ci;
        o{i} = oi;
        p{i} = pi;
        cf{i} = cfi;
    end
    c = set(c,'Magnitude',n,'Chroma',p,'ChromaClass',cc,...
              'ChromaFreq',cf,'Register',o);
    c = modif(c,option,chromascale);
    c = {c orig};
end

   
function c = modif(c,option,chromascale)
if option.plabel
    c = set(c,'PitchLabel',1);
end            
if option.cen || option.nor || option.wrp
    n = get(c,'Magnitude');
    p = get(c,'Chroma');
    cl = get(c,'ChromaClass');
    fp = get(c,'FramePos');
    n2 = cell(1,length(n));
    p2 = cell(1,length(n));
    wrp = option.wrp && not(get(c,'Wrap'));
    for i = 1:length(n)
        ni = n{i};
        pi = p{i};
        cli = cl{i};
        if not(iscell(ni))
            ni = {ni};
            pi = {pi};
            cli = {cli};
        end
        if wrp
            c = set(c,'Wrap',option.wrp);
        end
        n2i = cell(1,length(ni));
        p2i = cell(1,length(ni));
        for j = 1:length(ni)
            nj = ni{j};
            pj = pi{j};
            clj = cli{j};
            if wrp
                n2j = zeros(option.res,size(nj,2),size(nj,3));
                for k = 1:size(pj,1)
                    n2j(clj(k)+1,:,:) = n2j(clj(k)+1,:,:) + nj(k,:,:);  % squared sum (parameter)
                end
                p2i{j} = chromascale';
            else
                n2j = nj;
                p2i{j} = pi{j};
            end
            if option.cen
                n2j = n2j - repmat(mean(n2j),[size(n2j,1),1,1]);
            end
            if option.nor
                n2j = n2j ./ repmat(vectnorm(n2j,option.nor) + ...
                    repmat(1e-6,[1,size(n2j,2),size(n2j,3)] )...
                    ,[size(n2j,1),1,1]);
            end
            n2i{j} = n2j;
        end
        n2{i} = n2i;
        p2{i} = p2i;
    end
    c = set(c,'Magnitude',n2,'Chroma',p2,'FramePos',fp);
end


function c = freq2chro(f,res,origin)
c = round(res*log2(f/origin));


function f = chro2freq(c,res,origin)
f = 2.^(c/res)*origin;


function y = vectnorm(x,p)
if isinf(p)
    y = max(x);
else
    y = sum(abs(x).^p).^(1/p);
end

Contact us at files@mathworks.com