Code examples

Note

Familiarity with TDT’s real-time processor visual design studio (RPvds) is required to follow the examples below. See the RPvds manual for more information.

Walkthrough of a simple play/record circuit

The following example is based on this RPvds circuit (download circuit). If you wish to test the circuit you may need to adapt it for your specific device (e.g. on the RX6 the correct input channel for the microphone would be 128 and on the RZ6 you would use the AudioIn and AudioOut macros). The specifics for each device are described in TDT’s System 3 manual.

_images/record_microphone.png

Let’s start with a simple code example, using TDTPy, that loads a circuit and reads data from a buffer:

from numpy import arange, sin, pi
from tdt import DSPProject, DSPError
try:
    # Load the circuit
    project = DSPProject()
    circuit = project.load_circuit('record_microphone.rcx', 'RZ6')
    circuit.start()

    # Configure the data tags
    circuit.cset_tag('record_del_n', 25, 'ms', 'n')
    circuit.cset_tag('record_dur_n', 500, 'ms', 'n')

    # Compute and upload the waveform
    t = arange(0, 1, circuit.fs**-1)
    waveform = sin(2*pi*1e3*t)
    speaker_buffer = circuit.get_buffer('speaker', 'w')
    speaker_buffer.write(waveform)

    # Acquire the microphone data
    microphone_buffer = circuit.get_buffer('mic', 'r')
    data = microphone_buffer.acquire(1, 'running', False)
except DSPError, e:
    print("Error acquiring data: {}".format(e))

If you were to do the same thing using TDT’s ActiveX driver directly, the code would be much more verbose:

from win32com.client import Dispatch
try:
    # Load the circuit
    RX6 = Dispatch('RPco.X')
    if RX6.ConnectRX6('GB', 1) == 0:
        raise SystemError, "Cannot connect to hardware"
    if RX6.ClearCOF() == 0:
        raise SystemError, "Cannot connect clear device"
    if RX6.LoadCOF('record_microphone.rcx') == 0:
        raise SystemError, "Cannot load circuit"

    # Configure the data tags
    fs = RX6.GetSFreq()
    if RX6.SetTagVal('record_del_n', int(25e-3*fs)) == 0:
        raise SystemError, "Cannot set tag"
    if RX6.SetTagVal('record_dur_n', int(500e-3*fs)) == 0:
        raise SystemError, "Cannot set tag"
    if RX6.Start() == 0:
        raise SystemError, "Cannot start circuit"

    # Compute and upload the waveform
    t = arange(0, int(1*fs))/fs
    waveform = sin(2*pi*1e3*t)
    RX6.WriteTagV('speaker', 0, waveform)

    # Acquire the microphone data
    if RX6.SoftTrg(1) == 0:
        raise SystemError, "Cannot send trigger"
    last_read_index = 0
    acquired_data = []
    while True:
        if RX6.GetTagV('running') == 0:
            last_loop = True
        else:
            last_loop = False
        next_index = RX6.GetTagVal('mic_i')
        if next_index > last_read_index:
            length = next_index - last_read_index
            data = RX6.ReadTagV('mic', last_read_index, length)
        elif next_index < last_read_index:
            length_a = RX6.GetTagSize('mic') - last_read_index
            data_a = RX6.ReadTagV('mic', last_read_index, length_a)
            data_b = RX6.ReadTagV('mic', 0, next_index)
            data = np.concatenate(data_a, data_b)
        acquired_data.append(data)
        last_read_index = next_index
        if last_loop:
            break
    data = np.concatenate(acquired_data)
except SystemError, e:
    print("Error acquiring data: {}".format(e))

Compared with the code using the TDTPy module, code working with the ActiveX object directly requires a lot more boilerplate code.

Warning

Due to non-standard implementation of ActiveX in the TDT libraries, win32com defaults to an inefficient approach when calling certain methods in the ActiveX library. This results in a significant data transfer bottleneck. For more detail, and a description of how TDTPy solves this problem, see Brad Buran’s post.

Ok, let’s walk through the first example to illustrate how it works. First, we need to import everything we need:

from numpy import arange, sin, pi
from tdt import DSPProject, DSPError

Now, initialize the project and load the circuit, saved in a file named ‘record_microphone.rcx’ to the RZ6 DSP:

project = DSPProject()
circuit = project.load_circuit('record_microphone.rcx', 'RZ6')

Note that you can leave the default file extension off if desired. If the circuit is not in the current directory, you must provde an absolute or relative path to the circuit.

The circuit has the buffers mic and speaker as well as the tags record_dur_n and record_del_n. Note that some tag names end in _n. This is a special naming I use to remind myself what units these tags require (‘n’ indicates number of ticks of the DSP clock while ‘ms’ indicates milliseconds). Both mic and speaker have two supporting tags, speaker_i and mic_i, respectively, that are used by TDTPy to determine how much data is currently in the buffer.

The circuit is configured to deliver the data stored in the speaker buffer to DAC channel 1 (which is connected to a speaker) and record the resulting microphone waveform. The entire process is controlled by a software trigger.

We want to configure the microphone to record for a duration of 500 ms with a 25 ms delay. Remember that record_del_n and record_dur_n both require the number of samples. Since number of samples depends on the sampling frequency of the DSP, we have to convert our value, which is in millseconds, to the appropriate unit using tdt.DSPCircuit.set_tag():

circuit.set_tag('record_del_n', int(25e-3*circuit.fs))
circuit.set_tag('record_dur_n', int(500e-3*circuit.fs))

Alternatively, we can use a convenience method, DSPCircuit.cset_tag(), that handles the unit conversion for us (n is number of samples):

circuit.cset_tag('record_del_n', 25, src_unit='ms', dest_unit='n')
circuit.cset_tag('record_dur_n', 500, src_unit='ms', dest_unit='n')

Or, if we just rely on positional arguments (which we use in the example above):

circuit.cset_tag('record_del_n', 25, 'ms', 'n')
circuit.cset_tag('record_dur_n', 500, 'ms', 'n')

All three of the approaches are fine; however, we recommend that you use DSPCircuit.cset_tag() whenever possible since this makes the code more readable.

To write a 1 second, 1 kHz tone to the speaker buffer, we first generate the waveform using the sampling frequency of the circuit. The sampling frequency is available as an attribute, fs of the DSPCircuit class. A method, DSPCircuit.convert() facilitates unit conversions that are based on the sampling frequency of the circuit (e.g. duration*fs will convert duration, in seconds, to the number of sample required for the waveform):

t = arange(0, circuit.convert(1, 's', 'n'))/circuit.fs
waveform = sin(2*pi*1e3*t)

Then we open the speaker buffer for writing and write the data to the buffer. The first argument to DSPCircuit.get_buffer() is the name of the tag attached to the {>Data} port of the buffer component and the second argument indicates whether the buffer should be opened for reading (r) or writing (w):

speaker_buffer = circuit.get_buffer('speaker', 'w')
speaker_buffer.write(waveform)

Now that you’ve configured the circuit, you are ready to run it and record the resulting waveform. The DSPBuffer.acquire() method will block until the running tag becomes False then return the contents of the microphone buffer:

microphone_buffer = circuit.get_buffer('microphone', 'r')
data = microphone_buffer.acquire(1, 'running', False)

Accessing the raw ActiveX object

Although DSPCircuit and DSPBuffer expose most of the functionality available via the ActiveX object, there may be times when you need to access it directly. You may obtain a handle to the object via tdt.util.connect_rpcox():

from tdt.util import connect_rpcox
obj = connect_rpcox('RZ6', 'GB')