IQdata (IQ samples)

The IQ Data object handles many operations related to multi-channel complex samples.

Warning

The channel numbering starts from 0.
For example, an IQData object where getChannelCount() == 2 :

  • the fist channel has index 0
  • the second channel has index 1.
class IQData {
     constructor IQData( [string name] );

     dump() ; // prints details on the std out
     forceFloat(); // make sure internal data is in float format and not in original format
     number getChannelCount() ; // number of channels in the data , by default 1

     // load IQ samples from an existing file, supported formats : CS16, CF32, WAV
     boolean loadFromFile( string filename ); 
     // Save IQ Data to a file, supported formats : CS16, CF32, WAV
     boolean saveToFile( string filename  , [option:channel]); 
     // append IQ Data to a file, supported formats : CS16, CF32 (no WAV)
     bool appendToFile( string filename  , [option:channel]); 

     getSamples( int start, array ,[option:channel]); - where array : a = new Float32Array(2*size)
     setSamples( int start, array ,[option:channel]);

     Samples getReal([option:channel])  ; // returns real part
     Samples getImag([option:channel])  ; // returns imag part

     freqShift( number freqHz );

     number getLength() ; // length in samples
     setLength( number of samples , number_of_channels) ; // truncate or extend

     number getSampleRate();
     setSampleRate( number );  // change the samplerate

     number getCenterFrequency();
     setCenterFrequency( number );

     number getTimestamp(); // returns the timestamp for the first sample of this block (milliseconds since 01/01/1970)
     number getDuration() ; // returns the duration in seconds ( number of samples / sample rate)
     Object getGPS() ; // returns coordinates associated to this IQ Data block

     number rms( [option:channel] );

     object getAC( length , [option:channel]) ; // returns autocorrelation object
     object getPowerSpectrum( spectrum_width , [option:channel]);
     bool   CSVSpectrum( spectrum_width, filename, [option:channel]);

     IQData part( number sart, number samples , [option:channel]); // extract a subpart of the IQ
     bool append( IQData );

     readFromQueue( IQQueue object ) ; // replaces current samples with what is in the queue - waits (locks task)

     FloatData getReal() ; // returns the real part in a FloatData object
     FloatData getImag() ; // returns the imaginary part in a FloatData object

     // Attribute management
     bool setAttribute( JSONObjet ); // Add an attribute JSON object to the IQ Data
     JSONObjet getAttribute();       // retrieve attribute object

     Object toJSON( start, count ) ; // convert to a Javascript representation the internal data, from start to start+count

}

Memory management:

memory

Samples are stored in the host computer RAM. Only a reference (a memory pointer) to the data is stored in the task memory.
The Virtual Machine tries to keep the data in the original format as long as possible and convert it to float only when required.
Typically :

  • A capture from a radio device will generate integer-type data, the IQData pointer will reference integer data,
  • As soon as you want to access the data as floats (typically call a getPowerSpectrum() function for example), data is first converted to floating point.

Note: Default samplerate for IQ object is 1Msps. Use IQ.setSampleRate(SR_Hz) command to set samplerate for a given IQ object.

  • Example :
// Define and load IQ object
var o = new IQData('');
if( !o.loadFromFile('/tmp/dvbs.cf32') ) {
    exit(); 
}
// Set frequency and samplerate
o.setCenterFrequency( 100 );
o.setSampleRate(8e6);

print('we have '+ o.getLength() + ' samples recorded at '+ o.getCenterFrequency() + ' MHz, Bandwidth : ' + o.getSampleRate() / 1e3 + ' kHz');
print('file length:' + o.getDuration() + ' secs.');

// Extract 1024 sample from offset 10 ,as array (1024 Isamples and 1024 Qsamples)
var rawIQ = new Float32Array(1024*2);
var n = o.getSamples( 10, rawIQ );
print('extracted ' + n + ' IQ Pairs');
We have loaded ' + satlist.length + ' sat definitions.');

.dump

Display details on IQ object (in the VM console).

var o = new IQData('');
if( !o.loadFromFile('/tmp/dvbs.cf32') ) {
    exit(); 
}
o.dump();

.getChannelCount

Returns the number of channels contained in the IQData internal samples. For example, you may collect the two RF inputs from a Nuand BladeRF board. In this case the IQ data would contain two channels.

var rx = ...
var IQ = rx.CaptureAllChannels();
var channels = IQ.getChannelCount();

.getLength

Returns the number of samples stored internally.

  • Example
var o = new IQData('');
o.loadFromFile('/opt/sdrnode/test.cf32');
print('We have loaded ' + o.getLength() + ' samples');

.setLength

Define length (samples number) of an IQ object. If the object is empty, the number of channels can be specified with the optional second argument

     setLength( number of samples [,number_of_channels] ) ;

.getSampleRate

Returns samplerate (Hz) of an IQ object

:!: If undefined, default samplerate is 1Msps

 .getSampleRate();

.getCenterFrequency

  • Get center frequency of IQ object, value returned in MHz.
.getCenterFrequency();

.setSampleRate

Define a samplerate for an IQ object

.setSampleRate(SR_Hz);
  • Example
var o = new IQData('');
o.loadFromFile('/tmp/dvbs.cf32');
o.setSampleRate(8e6);

.setCenterFrequency

Set the senter frequency (MHz) of an IQ object

.setCenterFrequency(Freq_MHz);
var o = new IQData('/tmp/test.cf32');
o.setCenterFrequency(98);
var x = o.getCenterFrequency();
print(x);

.getDuration

Get IQ object duration, based on (number of samples)*(1/sample_rate)

Warning

You have to define samplerate ''.setSampleRate(SR_Hz)'' first.

  • Example:
    var o = new IQData('');
    o.loadFromFile('/tmp/dvbs.cf32');
    o.setSampleRate(8e6);
    print('Size:' + o.getLength() + "  - SR: "  o.getSampleRate() + "  - duration: " o.getDuration() + ' secs.');

.getGPS

Returns geographic position from GPS - if available. The returned object contains the following fields :

{
    "gps_fix" : boolean,
    "latitude_N" : number,
    "longitude_E" : number,
    "altitude" : number
}
  • Example:
    var IQ = rx.Capture( 1000 ); // capture 1000 samples from RX
    var pos = rx.getGPS();
    if( pos.gps_fix == true ) {
        print('Received at ' + pos.latitude_N + "," + pos.longitude_E ) ;
    }

.getTimestamp

Get UNIX timestamp of an IQ block (milliseconds since 01/01/1970)

var x = new IQData('x');
var timestamp = x.getTimeStamp();

.rms

Returns the log RMS power of the IQ data.

  • The returned value is : 20*log10( rms_power ),
  • The rms is computed as : mailboxes

using all the samples from the IQData object

.loadFromFile

Load IQ object from file.

var o = new IQData('');
if( !o.loadFromFile('/opt/sdrnode/dvbs.cf32') ) {
    exit(); 
}

.saveToFile

Save IQ object to file. Exisiting file is replaced (use .appendToFile() to add samples to exisiting file )
In case of a multiple channel IQData, the channel to be saved has to be specified (otherwise, only channel 0 will be stored).

Following formats are handled by recognizing their extension name.

-.CF32 ou .FC32 ou .IQ32 : Complex float single precision (8 bytes by sample, 4 for I, 4 for Q)
-.CS16 : Complex signed integer : 16 bits
-.CS8 : Complex signed integer: 8 bits
-.WAV : format Audio stereo
-.SDR : Custom format mixing metadata and IQ values, support multichannels datas

.saveToFile(filename, [option:channel]);
  • Example:
radio.setRxCenterFreq( 466 );
var IQ = radio.Capture( 10000 );
IQ.dump();
IQ.saveToFile('capture.cf32') ;

.appendToFile

Similar to .saveToFile() except appends to data to file if exisiting. In case of a multiple channel IQData, the channel to be saved has to be specified (otherwise, only channel 0 will be stored).

Following formats are handled by recognizing their extension name.

-.CF32 ou .FC32 ou .IQ32 : Complex float single precision (8 bytes by sample, 4 for I, 4 for Q)
-.CS16 : Complex signed integer : 16 bits
-.CS8 : Complex signed integer: 8 bits
-.SDR : Custom format mixing metadata and IQ values, support multichannels datas

Warning: WAV file format is not supported in current versions.

.getPowerSpectrum

This function computes the average power spectrum using the Fast Fourrier Transform. An object is returned :

{
 "datatype": "power_spectrum",
 "fft_size": your_fft_size,
 "sample_rate": radio_sample_rate_Hz,
 "channel": radio_channel_used,
 "timestamp": number, /* number of milliseconds since 01/01/1970 */
 "position" : {
 "gps_fix": boolean,
 "latitude_N": number,
 "longitude_E": number,
 "altitude" : number
  }
  "peaks" : [ array of peaks, see below ],
  "spectrum": []
}
var IQ = radio.Capture( 10000 );
var spectrum = IQ.getPowerSpectrum( fft ) ; 
peaks = spectrum.peaks ;
print('Peaks object:' + JSON.stringify( peaks ));
[
 {"frequency":0, "value":-99.626,"index":256},
 {"frequency":-0.038, "value":-114.254, "index":158},
 {"frequency":-0.047, "value":-114.162, "index":133},
 ...
]

.CSVSpectrum

Compute the average power spectrum using the Fast Fourrier Transform and export to CSV file.

  • If the file does not exist, a header line is added first,
  • In case the specified file already exists, the values are added as a new line.
bool CSVSpectrum( fft_size, filename, [option:channel]) ; 

Parameters:

  • fft_size number, size of the Fast Fourrier Transform to be used.
  • filename string, the file name.

This function returns true if the file has been written, false otherwise.

Example :

var rx = Soapy.makeDevice( {'query' : 'driver=rtlsdr' });
rx.setRxSampleRate( 2e6 );
rx.setRxCenterFreq( 105.5 );
for( var i=0 ; i < 100 ; i++ ) {
 var samples = rx.Capture( 2e6 ) ;
 samples.CSVSpectrum( 1024, 'test.csv');
}

.getAC

.getAC( length , [option:channel]) ;

This function computes the autocorrelation normalized coefficients for the IQ data using the given length.

  • returns autocorrelation object

.getSamples

Extract samples from IQ object, from position 'start'
Array format : a = new Float32Array(2*size)

getSamples(start, array)
  • Example
var o = new IQData('');
o.loadFromFile('/opt/sdrnode/test.cf32');
print(o.getLength() + ' samples');
var rawIQ = new Float32Array(1024*2);
var n = o.getSamples( 10, rawIQ );
print('extracted ' + n + ' IQ Pairs');

.setSamples

.part

Extracts a subset of samples from an IQ object. For multi-channel IQ objects, an optional integer has to be provided to set from which channel the data has to be extracted.
First channel is at index 0.

.part(offset_start,size, [option int channel]);
  • Example
var o = new IQData('');
o.loadFromFile('/opt/sdrnode/test.cf32');
// get 1024 complex from o, starting at index 1024
var subdata = o.part( 1024, 1024) ;

.append

Append IQ samples to existing IQ object

.append(IQ_data);
  • Example :
var IQ = DSP.tone( 440, 1000, 12500 );
IQ.append( DSP.tone( 880, 1000, 12500) );
IQ.dump();
IQ.saveToFile('/save/tone.cf32');

.freqShift

Shift center frequency using offset : freq_Hz. The object sample rate is used to compute the Local Oscillator value to be applied. Make sure the object has a valid samplerate (>0) before call

.freqShift(freq_Hz);
// load the searched signal
var IQ = new IQData('');
IQ.loadFromFile('/tmp/signal.wav');
// shift the searched signal
IQ.freqShift( -300 );
IQ.dump();

.setAttribute

Attach a JSON object to the IQ Data. This will be forwarded to queues.

Example :

var IQ = DSP.tone( 440, 1000, 1000 );

var x = { 'field_a' : 4, 'field_b' : 12 } ;
IQ.setAttribute( x );

.getAttribute

Extracts attribute associated to IQ datas.

Example :

var q = Queues.create( 'queue');
// Get a block 
var IQ = q.dequeue(true); // blocking mode, we wait to have something 
var x = IQ.getAttribute();

Last update: May 21, 2024