1.4 Working with VIBES objects

Let us now discuss the object-oriented workflow of the VIBES Toolbox. Object-oriented programming (OOP) is a popular paradigm in software development, which is perfectly suited for sound & vibration analysis in MATLAB. It is not necessary to be an expert in the programming part; rather it is important to know how to use the pre-designed classes of the VIBES Toolbox for your purpose.

To start, we will have a look into the vibes.TimeSeries class. Objects from this class can be seen as a representation of a stream of time data, which has multiple channels with (measurement) quantities, a time vector with length and sampling rate and perhaps some descriptive properties. In standard MATLAB use, this probably means having several variables in your workspace, each representing some part of the abovementioned information. With regard to data representation, the vibes.TimeSeries object can be seen as a struct that contains all this data under one variable.

% Create a TimeSeries object
TS = vibes.TimeSeries();

% Create a time vector for 0-8 sec @ 1000 Hz
fs = 1000;
dt = 1/fs;
t = 0:dt:8;

% Create two sine waves with random noise
y = sin(2*pi*[45; 250]*t) + 0.1*rand(2,numel(t));

% Assign the data to the TimeSeries object
TS.Time = t;
TS.Data = y;

% Set a name to the dataset
TS.Name = 'Sample data';

% Clear all variables except TS
clearvars -except TS

We have now created some data and put it into the TS variable, which is of class vibes.TimeSeries, and cleared all variables that we used in the meantime (just for the sake of illustration). You can display the object and see a summary of the object structure.

 
% Display the object in the command window
disp(TS) 

The object properties are shown organized in categories in such a way that one can quickly read the important information from the display. Some properties, such as the Time vector, are not shown in full: by clicking on All properties, these are revealed too.

While constructing the vibes.TimeSeries object, two vibes.Channel objects have been created too. These fields contain descriptive information on the rows of the data array, which are the (measurement) channels. Let us inspect these too:

TS.Channels

It is good practice to provide complete channel information including quantities and units, which can be done by setting the properties of the channels. We will use different approaches to do that:

% Get the channels as a separate variable
ch = TS.Channels;

% Set properties of each channel individually
ch(1).Description = 'Sine wave at 45 Hz';
ch(2).Description = 'Sine wave at 250 Hz';

% Assign a single value to both channels
ch.set('Quantity','Acceleration');

% Disperse different values over the two channels
ch.setArray('NodeNumber',[1 2]);

% Also set the position and directions
ch.setArray('Position',{[1 0 -0.4];[1 0 -0.4]},'Direction',{'+X';'-Z'});

% Display the effect for the channels in the TimeSeries object
TS.listChannels

Handle behavior of VIBES objects

The fact that these changes are seen in TS may come as a surprise, as the channels were not re-assigned into the TS variable. This reveals the very nature of the object-oriented way of working: the variable ch is actually a reference to the channels that hosted in the object TS, meaning that the ones in the object and the separate are the same. This is checked by comparing their so-called handles:

disp(TS.Channels == ch)

Most classes in the VIBES Toolbox are indeed handle classes, which all follow this behavior. Obvious exceptions are all simple types such as double, char and struct, which are value-typed by nature. Some implications are shown here:

% Assignment to a new variable > still the same object
TS2 = TS;
disp(TS2 == TS)

% Copy to an actual new instance > creates a new object
TS3 = TS.copy();
disp(TS3 == TS)

% The channels are handle classes, so these are still the same "references"
disp(TS3.Channels == TS.Channels)

% If really needed (often not), a deep copy also creates copies for the
% direct handle classes
TS4 = TS.deepCopy();
disp(TS4.Channels == TS.Channels)

Often-used methods such as vibes.TimeSeries/crop will create a new instance if an output variable is provided; otherwise the operation is performed on the original object. For subset selection vibes.TimeSeries/subs is a synonym for vibes.TimeSeries/crop, but it creates a separate instance regardless of output.

% Crop the original object to the interval of 3-5.5 seconds
TS.crop([],[3 5.5]);

% Get a subset for only channel 2
TS_ch2 = TS.subs(2);

Operations on VIBES objects

Slowly, we have moved into using a second strong suit of objects, which is their class-specific operations known as “methods”. Methods are just functions, but they operate on objects and can be called by the syntax out = obj.methodname(arg1,arg2,...). Let us review the methods available for the vibes.TimeSeries class:

methods(TS)

Most of the methods have their own documentation, which is best found by typing vibes.doc and pressing TAB to activate auto-completion, for instance:

vibes.doc('vibes.TimeSeries.plot')

% More concise help is available in the command window using the familiar
% help command:
help vibes.TimeSeries.plotSumLevel

Let us now try some methods on the object. The following lines should be rather self-explaining:

% Create a "named" figure and clear
vibes.figure2d('Time Series'); clf

% Plot channel 1
TS.plot(1);

% Also plot a sum-level of channel 1 (with default settings)
hold on
[h,Opts] = TS.plotSumLevel(1);
legend show

% Play-back the audio over the left and right audio channel
TS.play([1 2])

% Create a summed variant over the channel (first) direction
TSsum = sum(TS,1);

% Merge into the original set
TS = cat([TS; TSsum]);

Example of a time series plot

You can now see how one does not need to provide “boiler-plate” code such as xlabel, ylabel, title, nor any for loops over matrix dimensions: the VIBES Toolbox does this for you based on the data and meta-data set in the object.

Converting between VIBES object

Many classes in the VIBES Toolbox can convert to each other using methods that start with from* and to*. Here is a typical example to get to the frequency domain in two steps:

% Slice into time blocks of 0.5 sec > creates a TimeBlocks object
TB = TS.toTimeBlocks([],0.5);

% Compute FFT with a hann window and for 0 to 500 Hz > creates a FreqBlocks object
FB = TB.toFreqBlocks('hann',0:500);

% Review result
disp(FB)

Of course, the resulting vibes.FreqBlocks object has its own methods for plotting. Although the syntax is quite similar, the plots are very different:

% Plot the second channel with sine at 250Hz
vibes.figure2d('Spectra'); clf

% Plot a 3D waterfall diagram of the summed (3rd) channel
subplot(2,1,1)
FB.plot3d(3)

% Plot a 2D spectrum of the first block
subplot(2,1,2)
FB.plot(3,1,'label','')

% Also add an averaged spectrum over all blocks and a sum level
hold on
FB.plot(3,[],'averaging','mean','label',': ')
FB.plot(3,[],'plottype','sum','averaging','mean','label',': ')
ylim([1e-4 1e1])

Example of a frequency blocks plot

Loading and saving object data

VIBES objects can be loaded and saved from and to custom locations using load and save methods. A repository system can be used to locate files on relative paths for easy project management and sharing. Have a look at the following functions and methods:

  • vibes.browse
  • vibes.load
  • vibes.fullfile
  • vibes.TimeSeries/save

    Contact us for more VIBES

    Contact our support team or call us on +31 85 744 09 70

    ×