Data AcQuisition And Real-Time AnalysisScope - Spectrum - Spectrogram - Signal Generator
Software for Windows
Science with your Sound Card!
Contact us about
Most such macros appear to run "instantly" in real-time, since they are fast enough to run completely in the Trace Update Interval between display updates (waveform, Spectrum, Spectrogram, etc.) without noticeably delaying the display.
But sometimes you want a macro to take action on every display update. In simple cases, you can use WaitTrace in a loop to wait for an update at the end of each loop pass, then go back and re-run the loop operations.
However, if you want to run multiple independent macros on each display update, the WaitTrace method quickly becomes very hard to manage. This is especially true if you want to turn some macros off or on, independent of the others.
A cleaner approach is to assign each macro as a separate task, which will run whenever the display is updated. New tasks may be added, or existing ones removed, at any time. Up to 16 such tasks may run concurrently.
Please note: Since tasks run only when the display is updated, the Trigger settings may affect the update rate. For the maximum update rate, at the expense of a stable display, you can toggle Trigger off to get a free-running display. Further speed increases may be obtained by reducing the Trace Update interval.
Note, however, that the actual trace update rate will be limited by the time required to draw the trace on the screen. The fastest updates will be with Sgram active, since only a single vertical column is drawn on each update.
Task="_MyMacro" adds a previously-defined _MyMacro to the set of running tasks. The quotes are mandatory. Task="-_MyMacro" removes the same macro from the task set when you are done with it. The macro itself is not deleted from the Macro List, just from the internal task set, so it stops being run automatically on each task update.
Only one instance of any particular macro is allowed in the task set at any time. Attempts to install additional copies will be ignored. Attempts to remove a macro that is not in the set will also be ignored.
The task set is not directly viewable as a list of names, so to find out if _MyMacro is currently included you first use Task="?_MyMacro", followed by IF.Task=1.
Notice that you will need at least two separate macros in order to use multitasking: the task macro, and a separate macro that installs and removes the task macro from the task set. The install-and-remove macro might look something like:
Task="?_MyMacro" IF.Task=1 Task="-_MyMacro" ELSE. Task="_MyMacro" ENDIF.
When you first run this macro, before _MyMacro has been installed, Task="?_MyMacro" will return zero. The IF will fail, so the ELSE branch will install _MyMacro. Run this same install-and-remove macro later and the IF will be true, so _MyMacro will be uninstalled.
Alternatively, you can use separate macros to install and remove a task macro.
If a Custom Controls Dialog is used to adjust parameters for the task macro (such as cutoff frequency for a digital filter, or channel selection or reference frequencies for a Lissajous figure display), then you may want to remove the task macro whenever the dialog is closed.
IF.Mtr0?E=1 Task="-_MyMacro" ENDIF.
The tasks discussed above are invoked on every trace update, after all input (or output) data acquisition and processing, but just before it is displayed. However, there may be special cases where you want to modify the data before it is processed.
To install such a task, use Task="*_MyMacro". Note that the '*' is not part of the _MyMacro name, but is only used to indicate that this is to run as a pre-process task. Uninstall this task with Task="-_MyMacro", just as for other tasks.
Typically, a pre-process task involves copying the raw waveform (not spectrum or average, since this is before processing) into a Macro Array buffer, applying the desired modifications to the buffer, then uploading the buffer back to replace the raw waveform. For example, if you want to multiply each point of the Right Input (channel 1) by a factor held in variable K you could use:
Buf0="<=W1" Buf0="<*(K)" Buf0="<uW1"
This completely replaces the original raw data, and is acted upon by the subsequent processing and display operations. The waveform will be K times larger, as shown by the display, cursor readouts, Voltmeter, saved .TXT file, etc. Any spectrum or average from this waveform will likewise be K times larger, and the Spectrogram (Sgram) will be colored accordingly.
Please note that this will not affect Direct-to-Disk (DDisk) recording, since that process acts on the raw sound card data independently.
Macro timer commands that involve waiting (WaitSecs, WaitTime, and WaitCyclic, as well as non-timer Event Wait commands WaitKey, WaitPause, WaitAvg, WaitChange, and WaitTrace, plus the WaitMsg message box with buttons) are not allowed in tasks, and will be ignored if given there. But there are other ways to create timed tasks.
Although an installed task macro will be invoked on every trace update, the macro itself can decide if it should take action on any given invocation. For example, if it is a Custom Meter display, you may want to slow its update rate to prevent a blur of rapidly-changing digits. You can do this by having the task macro count the times it is invoked, and only update the meter every Nth time.
The installer macro can set N to the desired value, and also set a counter variable C=N. Then the task macro can do something like this:
C=C-1 IF.C=0 ... (Do update here) ... C=N ENDIF.
(However, see Meter Time Constants near the end of the Custom Meters topic for another, and usually better, way to slow the meter response.)
The above counter method does not use a true timer. Note that task macros can't use settable timers like WaitSecs, WaitTime, or WaitCyclic, but they can use read-only timers like the high-resolution Timer or UTC Time. For example, to delay one second between tasks, you could use T=Timer in the installer macro to initialize T, then use this in the task macro:
IF.(Timer - T)=>=1.00 ... (Do task here) T=Timer ... ENDIF.
The Timer used here has high resolution (typically 1 microsecond), but the overall resolution of this scheme is limited by the Trace Update Interval at best. The actual task update rate may be longer than the specified trace update if there are many complex traces to be displayed, or tasks that take a long time to execute. As noted in the Introduction, the fastest updates will be in Sgram mode.
A simple resizeable digital clock demonstrates how to use multitasking and Custom Meters. The task is _ClockTask, which can consist of as little as:
Mtr0=t IF.Mtr0?E=1 Task="-_ClockTask" ENDIF.
The Clock installer is merely:
The Mtr0=t line uses the t string variable to display the time in whatever format you have selected in Edit - Date/Time Preferences. The default is 24-hour time shown as hh:mm:ss.nnn. With a typical 10 msec Trace Update Interval, the decimal seconds digits are a blur. You can change the time format to hh:mm:ss to get a more useful display. However, this is still needlessly updating the display many times per second. This could cause a loss of performance in other tasks. You can use the previously-discussed Timer method to update only once per second:
IF.(Timer - T)=>=1.00 Mtr0=t T=Timer ENDIF. IF.Mtr0?E=1 Task="-_ClockTask" ENDIF.
By default, a running task shares variables with other tasks and with normal macros. (But see the Local Variables for Tasks topic below.) Because a task can interrupt a normal macro, it is not safe for the task to modify the same working variables, such as loop index or intermediate variables. Always use separate variables for the task.
However, since all tasks are run sequentially, they never interrupt each other. It's safe for another task to re-use working variables, as long as each task initializes them as needed.
But there are certain cases that require special consideration. For example, if you use a Custom Control to set the input or output Channel Select Ch to be used with (say) a wSig() or sSig() sigma function in a Custom Meter, then that channel will be used for all tasks and normal macros. If you want different tasks to use different channels, you need to take special steps.
If each task is hard-coded to use a certain channel, then at the start of the task it can save the current channel to a reusable variable (we'll use V here) with V=Ch. Then it sets Ch as desired, and at the end of the task it restores the original value with Ch=V.
This works because when a task is running, no other task or macro is running at that same time. A task may interrupt a running normal macro, but as long as the task leaves things the way they were found, there is no effect outside the task.
You can reuse the same variable V elsewhere for the same reason, as long as you never expect it to retain a value between tasks... only during a task.
Suppose there are two tasks, with different channels set by Custom Controls instead of being hard-coded in the tasks. The Custom Controls can set dedicated variables for the respective task channels, which must not be changed by any other task or macro. Let's say they are MIDI variables UA and UB, assuming that no MIDI operations are taking place. (UA and UB can hold integer values only... perfect for this use.)
The first task starts with V=Ch as before, followed by Ch=UA before the rest of the task, and ending again with Ch=V. The second task is the same but uses Ch=UB.
Note that for storing the Ch value, we don't really need floating-point variable V; we could just as well use MIDI integer UV or persistent fixed-point variable VarV, or anything we want.
Also note that data point functions Wv(), Sp(), and Av() can be used with an explicit channel selection parameter as well as a point index. When this is done the main Ch value is not affected, so these functions can be used in different tasks without saving Ch.
As noted in Multitasking Variable Usage above, tasks by default share variables with other tasks, and with normal macros. But if you have multiple tasks that run concurrently, it may be difficult to keep track of which tasks use which variables. This is especially true in the typical case where tasks are written for individual functions, without regard to how they may later be combined. It would be very tedious to have to go back at a later date and try to determine which macros need to have conflicting variable issues resolved.
The solution to this is to use local variables: On entry, your task can store all the original variables in a safe place, then re-use them for its own purposes. Just before it exits, the task restores the original variables.
The commands to do this are Buffer Block Save/Restore commands. Buf0#Bs=N saves all the main variables to Block N of Macro Array Buf0. A Block is a subsection of 100 elements; each Buf0 to Buf7 array can hold up to 10 such blocks, numbered 0-9. This copy command stores all floating-point variables A-Z, as well as all shared macro/MIDI integers and 32-bit fixed-point variables. The integers include U0-9, UA-Z, Q0-9, and QA-Z, while the 32-bit fixed point values include Ua-z and Qa-z.
When your task starts up, you could use, for example, Buf0#Bs=0 to save the main variables to Block 0 of Buf0 before you change them. If your task requires that some values (like counters) be remembered between updates, you can keep a separate set of "local" variables that you load to replace the saved main variables. You could use Buf0#Br=1 to restore those from Block 1 of Buf0.
Then the task does its job. Just before it exits, it would first save the local variables back with Buf0#Bs=1, then restore the original main variables with Buf0#Br=0.
Under this scheme, each concurrent task would have its own Block for local variables. All tasks can use the same block for initial storage of main variables, since once a task is started it can not be interrupted. When checking for compatibility, all you need to do is look at which blocks each task uses. If there is a conflict you only need to change the starting and ending code lines.
During development, you can edit the running task macro just like any other macro. The editor works on a copy of the macro, and doesn't update the working task macro until you hit Save in the Macro Edit dialog. Then any changes you have made will take effect when that task macro runs, on the next trace update. At normal trace update intervals (10 msec default), this appears to happen instantaneously.
This allows you to adjust the task in a "live" environment. Changes can be as simple as tweaking the number of decimal places shown on a Custom Meter, for example, or more fundamental changes to the underlying math calculations. This can be especially handy with calculations on a Macro Array that is displayed as a trace.
A bug in a normal macro results in that macro aborting with an error message. However, a bug in a task macro causes all tasks to be uninstalled. This prevents Daqarta crashing in certain cases where a problem in one task causes problems in other tasks.
See also Macro Overview
Questions? Comments? Contact us!We respond to ALL inquiries, typically within 24 hrs.
Over 35 Years of Innovative Instrumentation
© Copyright 2007 - 2023 by Interstellar Research
All rights reserved