JS-Xtract

Using the Object-Oriented framework

Object Oriented programming is a paradigm where data is stored inside something and is modified or controlled by using methods of that object. JavaScript is an object-oriented programming language since, literally, everything is an object. Even the number 1 has methods associated with it! The JS-Xtract framework enables object oriented view-points by wrapping data up inside easier to use objects. Whilst these objects remove a certain amount of flexibility the procedural calls give, they also make it easier to handle the data.

The object prototypes

There are 4 object prototypes which are of importance: TimeData(), SpectrumData(), PeakSpectrumData() and HarmonicSpectrumData(). All of these hold data for their given data types and define methods for extracting relevant features and transforming from one set to another. For instance calling TimeData()->spectrum() returns a new SpectrumData() object with the transformed data.

Constructors

The TimeData() has the following two constructors:


// Some Globals
var N = 1024;
var sample_rate = 44100.0;

// Creates a timeData with no sample_rate defined
// Automatically defines a data size of 1024 elements
var timeData = new TimeData(N);

// Creates a timeData with a sample_rate defined
// Automatically defines a data size of 1024 elements
var timeData2 = new TimeData(N,sample_rate);

If no sample_rate is set, then the sample_rate can be updated ONCE by calling TimeData()->sample_rate = x. If the sample_rate has been set, then an error will be returned. This is because features and other objects may have references to this data at that sample rate.

The SpectrumData(), PeakSpectrumData() and HarmonicSpectrumData() are effectively the same class chain and have the same constructors as the TimeData() except with their relevant object names, ie: new SpectrumData(N);. However if the sample_rate is undefined, then it defaults to being Math.PI. This way the frequency list is actually the radial frequency from 0 to half Pi. The sample rate can again be updated using the same method (SpectrumData()->sample_rate = x) but again only once.

Underlying memory

The following tables shows how much memory is actually set asside for each constructor

Prototype Underlying Memory Size (N) Bytes per N
TimeData() Float64Array N 8
SpectrumData() Float64Array 2N 16
PeakSpectrumData() Float64Array 2N 16
HarmonicSpectrumData() Float64Array 2N 16

The spectrum objects have 2N number of elements because they also hold the center frequency of each bin.

Moving data into the objects

Once an object has been created, the object can be populated by calling the copyDataFrom() function.

var N = 1024;
var wave = new Float64Array(N);
// Assume a frame of the time domain information is in wave.

// Construct an empty TimeData object
var timeData = new TimeData(N, 44100.0);

// Copy the data in
timeData.copyDataFrom(wave, N, 0);

copyDataFrom() takes three arguments: source array, number of elements to copy, the offset in the destination. The following show how to use those arguments to perform a multitude of copies


timeData.copyDataFrom(wave);
// Copies the data from wave up to either the length of timeData or the length of wave, whichever is sooner
// N = Math.min(wave.length, timeData.length)

timeData.copyDataFrom(wave, N);
// Copies the data from wave up to the number of N
// If N is bigger than either wave or timeData, then N = Math.min(wave.length, timeData.length);

timeData.copyDataFrom(wave, N, 8)
// Copies the data from wave up to the N element, storing them in timeData from the position 8.
// Since offset=8, then wave[0] is placed in timeData->_data[8].
// Again N is protected by using N = Math.min(wave.length, timeData.length+offset);

timeData.copyDataFrom(wave.subarray(12,N),N)
// Copies data from wave up to the N element, starting from index 12
// Actuall this is the same as the second call above, but we are effectively only passing in the wave array from element 12 onwards
// This way it is possible to perform the movement with a source offset.

In all of these cases, any unaffected elements are untouched! If we take the third example, where offset was at 8, then elements 0 through 7 are untouched and hold the data they previously held. This distinction allows TimeData to be used as a buffer or to update specific parts of the spectrum for instance. To clear the entire buffer, use zeroData() or pass in an array of zeros.

For the spectrum data, the copied array only includes the magnitude bins, the frequency center bins are left untouched.

When performing any of the data movement functions, the result node is cleared.

Results

When performing feature extractions, the results are stored inside the object as well as returned by the function call. For example:


var timeData = new TimeData(1024);

// Calculate the mean
timeData.mean();

timeData.results = {
    'mean': 0.4352
}

In this example the mean is stored in the results. This is of a key importance when we reconsider the chaining issues from the procedural calls. Since the mean is stored, in the following example that value is simply re-used:


// Calculate the variance
timeData.variance();

timeData.results = {
    'mean': 0.4352,
    'variance': 0.009434;
}

However, the object oriented advantage appears if we did not calculate the mean at all:


var timeData = new TimeData(1024);

// Calculate the variance
timeData.variance();

timeData.results = {
    'mean': 0.4352,
    'variance': 0.009434;
}

Because the data is copied in the object it's state can be guaranteed, therefore when intermediary values are calculated they can be stored for as long as the data remains the same. Likewise objects can actually hold references to each other:

var timeData = new TimeData(1024, 44100.0);

// Perform the FFT
timeData.spectrum();

// The resulting transform is stored as a new SpectrumData object in the results
timeData.results = {
    'spectrum': SpectrumData()
}

// Calculate the spectral centroid
timeData.results.spectrum.spectral_centroid();

// Display it in the SpectrumData object's result
timeData.results.spectrum.results = {
    'spectral_centroid': 1343.032;
}

If the TimeData object is then updated, the result node is deleted. If the SpectrumData node is not referred to somewhere else then it is garbage collected and deleted.

toJSON

For transmission or copying of the entire object a special toJSON() funtion converts the result node to a JSON string. This function is on each TimeData, SpectrumData, PeakSpectrumData and HarmonicSpectrumData. There are also toJSON functions for Float32Array and Float64Array objects.


var timeData = new TimeData(1024, 44100.0);

timeData.variance();

var str = timeData.toJSON();

str = '{"mean": 0.4352, "variance": 0.009434}';