/*********************************************************************************
** **
** D M A S T R **
** ––––––––––––– **
** **
** Original PDP–11 macro assembler version written by: **
** Kenneth Forster and Rod Dickinson **
** in 1975 at Monash University, Victoria, Australia **
** **
** DOS C version in 1983 written by: **
** Kenneth Forster and Mike Durham **
** With additional assistance from Kevin Ackley **
** **
** Turbo Graphic version and Enhanced Experimenter's Display by: **
** Jonathan C. Forster **
** in 1988 at the University of Arizona **
** **
** Win32 and DirectX version in 1997 with the **
** Direct3D renderer in 2014 and DirectShow VMR9 support in 2016 by: **
** Jonathan C. Forster **
** **
*********************************************************************************/
Introduction last updated 11/25/24 j.c.f.
Hit the space bar to continue or ESC to exit...
DMDX is the Win32 DirectX based version of DMASTR, a Mental Chronometry display system used in psychological, linguistic and other laboratories around the world to measure reaction times to visual and auditory stimuli.
While this introduction covers things from the very simplest use of DMDX through some of the most complex things possible it can't demonstrate the use of other media such as bitmap graphics, audio and digital video –– for them you will have to see the regular help or the demos (hit E for details). The beginner may only want to look at the first few screens of this introduction, others will probably want to see everything in here if for no other reason than to get an idea of the range of things DMDX can do –– even then we don't begin to cover all of what's possible, the sky's pretty much the limit and it just depends how keen you are.
If you want to just do the instruction & commenting section you can press I
or to do the scrambling section you can hit S
or for the positioning & Unicode section you can hit P
or for the 1 of N responses / mouse based 2D input sections you can hit 1 or 2
or for the sentence reading / using branching / simple counter sections you can hit G, B or O
or for the rating / AZK rating & calling / typing & more complex counter sections you can hit R, Z or C
or for the ISI & delay / abort display queue & counter display / multiple responses sections you can hit J, Q or U
or for the macro, variable ISI & multi–scramble / indexed branching / dynamic keyword sections you can hit M, X or Y
or for the test mode / tachistoscopic acid test / flicker fusion test sections you can hit T, A or F
or for the list serve & citation postscript you can hit L.
Hitting \ before any selection will resume the introduction there and ESC at any time will terminate it.
Otherwise hit the space bar to continue to the Basic Introduction...
Basic Introduction
People used to bug us to write a manual for DMDX and over the years a couple of people even tried to do so. However after sitting down and realizing just what it was they'd have to do they promptly gave up. Now that I'm retired and after finding out that DMDX use remains strong I thought I'd toss a simple introduction into the DMDX executable to help beginners and then one thing led to another and after 18 months or so we finally have what could be referred to as a manual rather than just an introduction. But first we have a technical issue you should address, namely Sticky Keys…
DMASTR's input is primarily a binary response paradigm and when DMTG (the Turbo Graphic DOS version of DMASTR) introduced the keyboard as an optional response device (prior to that custom switches wired up to an interface card were exclusively used) we decided to use the shift keys as the default response keys and the space bar as a request and there were no particular problems beyond the timing issues inherent to a device that polls a column of it's keys at a time (typical keyboards can have 15 milliseconds or more of variability). However when DMDX (the Win32 version of DMASTR) was introduced running under Windows 95 and 98 things were less sanguine as Microsoft had decided to add a feature called Sticky Keys where hitting a shift key five times in a row fires up a dialog about enabling it and blowing subject testing out of the water. This obviously needs to be disabled so you might want to do that now by hitting ESC and exiting DMDX and then hitting one of the shift keys five times and following the dialog tree down to where you can tell Sticky Keys to go away and never rear it's ugly head again.
DMDX is controlled by a script we refer to as an item file, this introduction being a special example run with the Introduction button, normally you would browse to an item file and click Run and DMDX will start executing it and we refer to that as running a job.
The Diagnostic options are to run the file as quickly as possible without subject responses to debug an item file using the item number as an RT, the diagnostic branching option being for item files that are using branches that are not based on the subject's RT (that won't be changing in a diagnostic run) and are instead based on counters and what not. The Subject ID is a string that will be copied into the saved data file to uniquely identify this run's data, the Unicode option is for item files that have Unicode characters in them and the Syntax Check button parses the file without displaying anything to check for syntax and other kinds of errors (like you having Unicode characters in your item file but not having the Unicode option active).
Once an item file is running ESC is used to either stop the job and save the data if the job has run to completion or to abort the job if it hasn't run to completion. After a run that has terminated in an error the Copy button can be used to copy the contents of the diagnostics displayed in the text box to the clipboard and last but not least the Ignore Unknown RTF check box has to be checked in almost all instances these days as it makes DMDX skip RTF control words it doesn't know as I don't know of an editor any more that doesn't produce them.
Item files in DMDX are Rich Text Files with the extension .RTF and it's pretty much WYSIWYG although there are countless options covered in the help to modify the display. As a brief tease of DMDX's capabilities we can show off the Unicode option's ability to render just about any textual thing you can get into a .RTF file that isn't an inline resource (like a graphic). A few years ago this would have been out of the question but after beating on the Unicode option for years I think I finally have it where it needs to be. In the next display we'll do a Hello World demo but in nineteen different alphabets, some of them right to left and generally as diverse a bunch as I could find that Google Translate knows (the item that produces that display is covered later on in the positioning and Unicode section). And if you can read any of them and I've made a silly error please see the list serve postscript at the end of this introduction and let me know on the DMDX list as Word was battling me pretty hard for a while there with the right to left fonts and some of them might have gotten mangled (Word 2016 seems to really dislike Malgun fonts that somehow wound up being used for all the right to left stuff instead of just the Korean one, Calibri is a much safer choice).
Speaking of mangling you may still have to use WordPad instead of Word in some instances as it's RTF is generally easier for DMDX to parse.
The first line of the item file is referred to as the parameter line and it can be extended to cover multiple lines with the extended parameters keyword <ep> and when the parameters are done with the end of parameters keyword <eop> is used. There is one non-optional parameter that must be specified and that is the frame duration that specifies how long a single display element of an item (a "frame") is displayed for, either in ticks (usually 1/60th of a second) or approximately in milliseconds (it will be converted to ticks internally). The parameter line for this item file is something like:
<ep> <id #keyboard> <vm desktop> <cr>
<msfd 166> <dwc 255,255,96> <dbc 32,64,128> <eop>
Here we've specified that the input device to be used for this job is the #keyboard device that doesn't use names and instead uses button numbers and works under all circumstances (the standard default keyboard device is less useful when the system language is not English). We've also made sure to the use the desktop video mode and we've also specified that the job should run continuously with <cr> and not stop for a request after every item. On the second line we've specified the default display duration is 166 milliseconds. In the bad old days (and indeed when I created this item file) that would have been <fd 10> because everything display related in DMDX is tick based (a tick being one vertical retrace interval, usually 1/60th of a second) however because this item file has to execute on all machines and refresh rates of 240 Hz have been seen running DMDX and I just saw ads for 480 Hz devices I've had to go back and rejigger all the timing in this introduction to be millisecond based.
And just because you can specify a time to the millisecond doesn't mean your display is going to be able to do that, times specified in milliseconds are converted to ticks internally (although I would note it is possible get arbitrary display durations with adaptive sync monitors using DMDX's freesync video mode modifier and when 480 Hz and even 1 kHz displays become available tick based timing is going to become effectively millisecond timing). We've also set the default writing color along with the default background color (normally it's black on white, this coloring scheme is a tribute to the original Borland Turbo C compiler that was used to create DMTG back in 1988 when such coloring schemes were trendy).
Note that when I wrote that paragraph the parameter line was in fact as shown (excepting the millisecond frame duration), these days it is quite a bit more complicated as more and more features were added to this introduction (like having to specify the delay between items and feedback duration in milliseconds) however I have no intention of getting into such deep things this early on if at all.
Normally after the parameters some instructions would be given but we'll get to that once we cover some more basic item structure details. The standard task for DMDX is a binary response lexical decision task and a generic example of an item in one is:
+101 * "target";
The first element in an item is the Correct Response indicator, or CR indicator, here we've specified that the right shift key is the correct response key with a +, if the correct response was the left key a - CR indicator would be used (other CR indicators are = for any, ^ for none, ! to make the whole item a comment and ~ to skip the display of this item). If there was no response to be gathered (for example in an instruction) the CR indicator can be skipped.
A special note about this introduction is that in order to display double quotes (") and semi-colons (;) I'm using full width versions of those characters found in Asian fonts as otherwise there's no way DMDX can display them so the spacing of these displays is going to be a little liberal (for instance there are no spaces between those brackets earlier in this sentence). I've since noticed that two Times New Roman apostrophes (") look just like a double quote so you'll find some of them in the text. If you happen to be reading the version of the introduction that appears in the help there I've turned all the full width characters into normal ones so people can copy and paste examples out of it and the spacing there will of course be normal...
Next up is the item number (here it's 101) that is used to identify responses in the .AZK data file DMDX produces when the data is saved at the completion of the run (not when it's aborted) and cannot exceed 2^31 (2147483648).
DMDX will stop after each item and wait for a request (by default that's the space bar for the #keyboard device we're using) unless continuous running has been used or <continue> occurs in the last frame of that item (we have <cr> in the parameters), however when the item number is zero DMDX will always wait for a request to dismiss that item (the defining characteristic of an instruction) unless of course <continue> has been used. Yep, it's complex, that's what you get with software that's been in use for half a century…
After the item number the individual display frames of the item follow although in our basic example there is just one frame. The first element of the frame is the asterisk clock on indicator that turns the subject response clock on when that frame is displayed. Following that is the text that will be displayed in double quotes followed by the item terminator semicolon. The next screen will execute this and two more similar items and you can respond with the left or right shift key or if you don't the item will time out after 4 seconds (the <Timeout> parameter can be used to override the default 4000 ms).
You'll notice that after each item DMDX provided feedback for each response, if you don't want it feedback can be turned off in the parameters with the no feedback keyword <nfb> (used in the Sentence Reading section later on) or if you wanted the display cleared after the response but no feedback per se you could use the clear feedback keyword <clfb> (used in the Indexed Branching section) or if you wanted the target left on the screen but also wanted feedback you could use the feedback only clears behind keyword <fbocb> (used in the Typing section) or if you only wanted wrong feedback you could use the wrong feedback only keyword <wfbo> (<cfbo> exists for correct feedback too) or if you didn't like the reaction time being displayed it could be suppressed with the no feedback time keyword <nfbt> and if you wanted different feedback entirely it can be specified as well with <cfb>, <wfb> and <tlfb> (used in the 1 of N Responses section).
Of course just presenting a single word and leaving it on the screen isn't very interesting so you might want to erase it by putting a frame separator (/) in there before the semicolon and because we haven't specified a unique frame duration for the first frame it would be displayed for the default frame duration (in this item file that would be 166 milliseconds from our <msfd 166> parameter) and then be erased. The next display will present three of these items:
+201 * "target" / ;
Note the order of the components of a frame generally isn't significant (the exception being when they contain tests used in conditional branching) whereas the order of the frames in an item is very definitely significant.
And of course even that's totally tame, so maybe we want to do some masked prime presentation with a fixation point:
+301 <msfd 1500> "++" / "##########" / <msfd 50> "prime" / * "target" / ;
Here we've used the millisecond frame duration keyword to make the fixation point stay on the screen for 1.5 seconds and we've specified the prime should be displayed for 50 ms, the other frames will pick up the default frame duration of 166 ms (the mask and target). The timing of that last blank frame is different in that when DMDX displays a frame (which takes one tick) and there are no more frames to be displayed in the item it goes about whatever it is that needs doing next (waiting for a response or request or preparing the next item), it doesn't wait around for the frame's implicit duration (here 166 ms). Even if you explicitly put a duration in there it will be ignored as all a duration does is specify the time that the next frame would be displayed at -- if there's no next frame it has no effect (instead assuming you didn't have any feedback you would have to manipulate the inter item interval with <delay>, as touched upon later in the Sentence Reading section and in more detail in the ISI & Delay section).
The next display will present three of these items.
While we're on the subject of frame durations I should mention there's a possible gotcha lurking in DMDX's syntax in that if you specify an enormously long frame duration (say you want to specify a 10 second display duration in milliseconds and instead of using <msfd> you've used <fd> that specifies the duration in ticks that are 1/60th of a second when you've got a 60 Hz display instead of a millisecond and that 10 second frame duration is now the better part of three minutes) DMDX won't be checking for you madly hitting the ESC key trying to get control back unless you invoke DMDX's first safe mode with <safemode 1> in the parameters:
<msfd 166> <safemode 1>
+401 <fd 10000> "++" / "##########" / <msfd 166> "prime" / * "target" / ;
The reason this safe mode is only provided as an option and not always active is that the original DirectDraw renderer could wind up overwriting the "Abort the Job" dialog as later frames were displayed, I'm reasonably sure the Direct3D renderer used with Windows 8 and later OSes doesn't do so (certainly recent testing doesn't indicate it does) so you are probably pretty safe these days turning it on regardless.
There are countless other options like <bmp> or <wav> that makes the text of the frame be used as a bitmap or wave file name to be displayed or played instead of the text itself being displayed what to speak of <dv> for playing digital video which can be seen in the demos:
https://psy1.psych.arizona.edu/~jforster/dmdx/demos.zip
https://psy1.psych.arizona.edu/~jforster/dmdx/DVDEMO.ZIP
Also in the too complicated to demo here bracket DMDX will also happily drive two monitors, one for the subject and one for the experimenter (use TimeDX's Video Driver selection to select the subject's display device), it'll even send the experimenter's display data to another machine running the Monitor program (see TimeDX's Network Setup command). Both of which greatly facilitate Naming Tasks where the experimenter is deciding if a subject's response is correct or not (see DMDX's <NamingTaskNegation> keyword). DMDX can also send signals to other machines with <output> be they fMRI or EKG/EMG or what have you over a parallel cable (see TimeDX's PIO Test) or with <send> over a TCP/IP network connection (see the Eye Tracker notes). You can also do remote testing over the internet with DMDX. You bundle DMDX up with WinRAR and it's SFX self extracting ability along with your item file and my poster.exe HTTP data posting program and a batch file to post the data to the UnloadAZK4web CGI-BIN running on psy1 and as long as your subjects are willing to use Windows computers you're gathering data from all over the globe -- see the Remote Testing section in the help, you'll want to use the Luxury Yacht solution...
Instruction Section
While a slash / is our temporal separator there is one instance where it isn't and it becomes more of a spatial separator and that's when the leading frame's duration is zero and here you'd wind up with two frames scheduled at the same time and indeed when the second frame doesn't erase the first this is in fact a very useful thing, say, in the case of instructions where you want more than one line of text on the screen at once. So useful in fact there's a special variant of the frame separator and it's the comma , which is exactly equivalent to <msfd 0> / <noerase> (or in classic Dmastr syntax using switches %0 / ! should you be so inclined) and it becomes a real spatial separator when you use the <line> keyword (or @ switch) to specify which line on the screen you want the text displayed on (where line zero's bottom is the middle of the screen). While there are better options for instructions that we'll get to shortly classic instructions might be something like this:
0 "Instructions are items with an item number" <line -1>,
"of zero and are usually dismissed by a request",
"(which is by default the space bar) unless the" <line 1>,
"<continue> keyword or C switch has been used..." <line 2>;
Instructions are items with an item number
of zero and are usually dismissed by a request
(which is by default the space bar) unless the
<continue> keyword or C switch has been used...
As noted earlier instructions used to be a bit of pain once one moved beyond a fairly terse level of instruction as one would have to guess how much text could fit on a line and re-wrapping stuff when you edited them could get tedious until I added the <inst> keyword to automatically format paragraphs. Here one specifies a bounding box on the screen (using fractions of the screen height and width) and one makes each word and it's following space a frame and uses the comma frame delimiter to merge them all together like this:
0 <inst .1, .1, .92 hardmargin>
"Lorem ", "ipsum ", "dolor ", "sit ", "amet, ", "ne ", "ubique ", "ridens ", "voluptaria ", "his, ", "in ", "augue ", "debet ", "facilisi ", "duo. ", "Oratio ", "assentior ", "sea ", "no, ", "eu ", "docendi ", "fastidii ", "iudicabit ", "mei. ", "Suas ", "rationibus ", "efficiendi ", "in ", "mei. ", "Vel ", "sonet ", "elitr ", "deleniti ", "an.",
<inst esc> "Hit the space bar to continue..." <line 12>;
When creating these <inst> items I like to type my sentences out normally then do a search and replace on all the spaces in them with [space]",[space]" thus automating the process. The hardmargin option makes DMDX issue a new line before any frame that would cross the right margin as opposed to the default behavior of doing so after it crosses the margin -- trust me, this is better.
Lorem ipsum dolor sit amet, ne ubique ridens voluptaria his, in augue debet facilisi duo. Oratio assentior sea no, eu docendi fastidii iudicabit mei. Suas rationibus efficiendi in mei. Vel sonet elitr deleniti an.
At the end of that instruction you'll notice the frame with <inst esc> in it, here we're escaping the positioning of that line from the normal <inst> rules and falling back on traditional mechanisms explicitly placing the space bar message on the 12th line of the display (zero being the middle, negative going up and positive down the screen).
And in addition to <inst esc> you can also put <inst nl> in to force a new line at that point and if you wanted a blank line you'd put another frame with another newline:
0 <inst .1, .1, .92 hardmargin>
"Lorem ", "ipsum ", "dolor ", "sit ", "amet, ", "ne ", "ubique ", "ridens ", "voluptaria ", "his, ", "in ", "augue ", "debet ", "facilisi ", "duo. ", <inst nl>, <inst nl>, "Oratio ", "assentior ", "sea ", "no, ", "eu ", "docendi ", "fastidii ", "iudicabit ", "mei. ", <inst nl>, <inst nl>, "Suas ", "rationibus ", "efficiendi ", "in ", "mei. ", <inst nl>, <inst nl>, "Vel ", "sonet ", "elitr ", "deleniti ", "an.",
<inst esc> "Hit the space bar to continue..." <line 12>;
Lorem ipsum dolor sit amet, ne ubique ridens voluptaria his, in augue debet facilisi duo.
Oratio assentior sea no, eu docendi fastidii iudicabit mei.
Suas rationibus efficiendi in mei.
Vel sonet elitr deleniti an.
These days you can put hardmargin in the parameters with <inst hardmargin> and then it's active everywhere and doesn't need continual invocation like this introduction has.
There's one other option for <inst> that I should mention and that is right2left which you have to use when using right to left fonts. In the old days getting that to work was a bit of effort, these days all you should have to do is use the appropriate justification (<xyjustification righttop> for instance) and the appropriate bounding box (say <inst .08, .1, .9 right2left>). In the same way that hardmargin can be used in the parameters right2left can similarly be used so it doesn't have to be specified everywhere. While your word order is probably going to be left to right in the item file by the time it's displayed it should all be correct -- unless you're having trouble with punctuation going on the wrong side of the words like I was having with the following Hebrew demonstration, here the age old hack of pouring your item file through WordPad appears to rectify it...
0 <inst .08, .1, .9 right2left> <xyjustification righttop>
"העיר ", "בשפה ", "משפטים ", "לוח ", "גם, ", "ביולי ", "אחרות ", "אנציקלופדיה ", "ב ", "עזה. ", "יוני ", "פולנית ", "תאולוגיה ", "על ", "שמו. ", "דת ", "שמו ", "קבלו ", "אחרים ", "הגרפים. ", "גם ", "מתוך ", "התוכן ", "לוח, ", "את ", "ארץ ", "נוסחאות ", "ליצירתה. ", "שפות ", "עזרה ", "המשפט ", "בה ", "מדע, ", "או ", "זכר ", "הבהרה ", "שימושיים ", "סוציולוגיה.",
<inst esc> "Hit the space bar to continue..." <line 12>;
Note if you're reading the online or help file version of this introduction not all browsers are going to display the above item correctly as only Chrome and Firefox understand the HTML <bdi> tag I have to use to get it to display the way your editor is likely to display it.
העיר בשפה משפטים לוח גם, ביולי אחרות אנציקלופדיה ב עזה. יוני פולנית תאולוגיה על שמו. דת שמו קבלו אחרים הגרפים. גם מתוך התוכן לוח, את ארץ נוסחאות ליצירתה. שפות עזרה המשפט בה מדע, או זכר הבהרה שימושיים סוציולוגיה.
Item files can contain comments and there are two varieties of them. The first is the keyword variant where any keyword (be it in the parameters or items) that begins with a ! becomes a comment. Beyond allowing you to chuck a comment in mid-item this is a convenient way to temporarily stop a keyword from having any effect, I will often use it to disable branch diagnostics in the parameter line for example when I might want to turn it on again later:
<fd 10> <!branchdiagnostics>
+1 "target" <! mid-item comment> * ;
The other form of a comment is the CR version of it where any item that begins with a ! becomes a comment and all of it's contents up until the semi-colon is ignored by everything except DMDX's scrambling routines (where it counts as an item):
! this is a comment;
It should be noted that like all items a comment can extend over multiple lines and is not a line based thing, a trap people seem to fall for a lot. So in the following the first line doesn't end in a semi-colon and instead the second line (that should be an item) is now a part of the first line's comment:
! this is a comment
+101 * "target" / ;
Scrambling Section
DMDX can randomize the order of items presented with its scrambling routines invoked with the scramble parameter <scramble>. The scramble parameter determines the size of the blocks scramble moves items around in, the block size being needed to prevent situations where any number of items from the same condition could be presented sequentially. If you don't care the value 1 or the number of items in the file will do, they function equivalently. The simplest possible item file with a scramble would then look something like this:
<msfd 166> <scramble 1>
+101 * "target A" / ;
+102 * "target B" / ;
+103 * "target C" / ;
A typical run with the above item file might produce:
<msfd 166>
+101 * "target A" / ;
+103 * "target C" / ;
+102 * "target B"/ ;
A more typical case would be where you had two conditions, let's say there are three items in each and we want to limit the presentation of items in one condition to two in a row. Here you'd specify a scramble parameter of two and you'd interleave the two sets of items:
<msfd 166> <scramble 2>
+101 * "target A" / ;
+201 * "target a" / ;
+102 * "target B" / ;
+202 * "target b" / ;
+103 * "target C" / ;
+203 * "target c" / ;
Here each block of two items would be considered individually, because we've just got two items they would either be swapped with each other or not and then the blocks of two would be moved randomly in the file and the results might look like this:
<msfd 166>
+202 * "target b" / ;
+102 * "target B" / ;
+203 * "target c" / ;
+103 * "target C" / ;
+101 * "target A" / ;
+201 * "target a" / ;
The results of any given scramble are available in the text file called scrambled.itm that should be in the folder the item file was in.
An item file with no instructions or an ending message† isn't realistic and of course those messages shouldn't be moved with the items as they're scrambled so there are fixed blocks that are delimited by pairs of dollar symbols (dollars in quoted text segments aren't seen by scramble):
<msfd 166> <scramble 1>
$ 0 "Instructions";$
+101 * "target A" / ;
+102 * "target B" / ;
+103 * "target C" / ;
$ 0 "The End";$
Note the positioning of dollars are unaffected by white space so you could just as easily have this instead:
$
0 "Instructions";
$
†While we're on the subject of ending messages I would note that for the longest while you couldn't collect data from the last item in an item file and I'm not sure that even with current versions of DMDX that you can in all circumstances so at the very least an ending message is a very good idea.
Next up we might have the case where you have pre-test items and post-test items and don't want them mixed up with each other yet still want them scrambled within their own section. Here we have the scramble emit command, a backslash \ by itself at the start of a line (if it occurs anywhere else it will be ignored, primarily because it's a valid path name character). What it does is force scramble to stop reading input and scramble everything read to that point, write it out and then start reading more input to scramble:
<msfd 166> <scramble 1>
$ 0 "Pre-test instructions";$
+101 * "target A" / ;
+102 * "target B" / ;
+103 * "target C" / ;
$ 0 "Post-test instructions";$
\
+201 * "target a" / ;
+202 * "target b" / ;
+203 * "target c" / ;
$ 0 "The End";$
On the right we have a possible result:
<msfd 166>
0 "Pre-test instructions";
+103 * "target C" / ;
+101 * "target A" / ;
+102 * "target B" / ;
0 "Post-test instructions";
+202 * "target b" / ;
+201 * "target a" / ;
+203 * "target c" / ;
0 "The End";
Often times items have to occur in pairs or other groups of sequential items and for this there's a grouping factor parameter <Grouping> that will treat any given specified group size of items as if they were one item for the purposes of calculating the number of items in a block and items are not scrambled within the group.
<msfd 166> <scramble 1> <grouping 2>
+101 * "target Aa" / ;
+102 * "target Ab" / ;
+201 * "target Ba" / ;
+202 * "target Bb" / ;
+301 * "target Ca" / ;
+302 * "target Cb" / ;
A typical run with the above item file might produce:
<msfd 166>
+101 * "target Aa" / ;
+102 * "target Ab" / ;
+301 * "target Ca" / ;
+302 * "target Cb" / ;
+201 * "target Ba" / ;
+202 * "target Bb" / ;
It's worth noting that if you remove the <scramble> parameter and then run the item file without removing the dollars delimiters and backslash emit commands you'll get syntax errors as they're removed by the scramble routines and if scramble isn't activated it's not going to remove them and while we could get DMDX to ignore them traditionally that's not the case. If you do have an item file with scrambling in it that you want to present in the same order every run you can use <scrambleseed> (see the next section).
Another thing to bear in mind is that the scramble routines are extremely semi-colon centric (from scramble's point of view an item is the semi-colon and the text preceding is incidental and hardly examined at all), if you have text in a scrambled item file that's not terminated with a semi-colon and there are scramble delimiters in it (so $ and \ symbols) scramble is likely to snip it into oblivion -- and that's when it's in a good mood, considerably more "entertaining" options are in play. It's ancient code and I've gone through it and made it much less hostile than it used to be† but if it's eating your lunch for what appear to be inexplicable reasons it's likely there's a missing semi-colon so going over your item file with a fine tooth comb is probably in order -- I've had graduate students come to me unable to figure out what's up with multi-hundred line item files and once I apply the programmer's eye sure enough there's a missing semi-colon even though they swear they've gone over it multiple times.
†For instance there's code in there now that should warn when scramble is going to snip stuff, whether it does so in all cases remains to be seen…
There are a couple of other scramble parameters like the aforementioned <ScrambleSeed> parameter that primes the random number generator to always produce the same sequence of numbers for any given seed thus always producing the same item order. There's also a variant of the grouping parameter called the <VariableGrouping> parameter allowing flexible groups of items where items are members of the same group as long as they are (a) sequential in the item file and (b) when their item numbers are divided by the variable grouping parameter using integer arithmetic the results are identical (so ignore the fractional remainder) . There is one trick I should mention should you not be able to use variable grouping yet have different numbers of items in your groups and that is to use a grouping factor of whatever the largest group of items is and pad all the others with empty comments (so !; for instance) that DMDX will skip over in minimal time yet because they're items they satisfy scramble's needs.
Should none of the above meet all your needs there's also Multi-scrambling where the item file is passed through the scrambling routines multiple times, however I'm not going to get into that here as most of the time it's brain tumor cultivation material and I don't think I'd be able to do it justice one screen at a time (google "dmdx multiscramble"), I do however use a simple instance of it later in the Macro section.
Positioning Section
There are times when just specifying the line that you want a display on isn't sufficient and for that we have <xy> where you provide coordinates for the frame to be displayed at, said coordinates either being a literal pixel value or a fraction of the screen's relevant dimensions as used in the instruction specifications earlier on. By default the coordinates are those of the top left corner of the frame's display, however while that's good for <inst> it's not too easy to use manually, especially when using different computers with different display dimensions, so when positioning frames it's best to specify the coordinates of the center of the displayed frame by using one of DMDX's different justification modes with <xyjustification center> (other values are lefttop, righttop, leftbottom and rightbottom).
As a demonstration let's consider the case of a multi-target item where you want to have four elements in the quadrants of the screen and the task is to decide if a target in the center of the screen matches or not:
-401 <xyjustification center> <msfd 1500> "++" <dfm 5> / "1" <xy .25, .25> , "2" <xy .75, .25> , "3" <xy .25, .75> , "4" <xy .75, .75> , <msfd 100> * "5" <xy .5, .5> / <dfm 1> ;
Because the task is to identify if the number in the center of the screen appears in the corners as well our CR indicator is - because the correct answer is no, the 5 is not at the corners. Next up we're specifying that the justification should reference the center of the image so that they're all nicely positioned and we don't have to bend over backwards figuring out what coordinates to use. We've also used <dfm 5> to set the font multipliers to 5 to make the numbers nice and big and the last frame has <dfm 1> in it so the feedback that follows will be the normal size...
Next up we have our actual positioning keyword <xy .25, .25> where we've asked for the "1" frame to be positioned a quarter of the way in from the top and left edges of the screen. Coordinates when passed to <xy> are either actual pixel values if they're 1 or more or if less than 1 relative fractions of the relevant dimension of the screen, a much more convenient thing to deal with all the different screen resolutions one's item file might encounter -- of course if we had bitmaps in there then they would change size with the different screen resolutions but there are ways of automatically scaling them too, see the Stretch Blt modes section in Bitmap notes in the help (google "dmdx bitmap notes").
Note that <dfm> no longer needs to be used solely in the frame before you want it to be active (although that option still has it's uses, not the least of which is to affect the feedback that follows the item), instead <dfm>, <dbm>, <dbgc> and <dwc> all have a STAT option that makes them take effect in the frame they're used in:
-401 <xyjustification center> <msfd 1500> "++" / <dfm 5 STAT> "1" <xy .25, .25> , "2" <xy .75, .25> , "3" <xy .25, .75> , "4" <xy .75, .75> , <msfd 100> * "5" <xy .5, .5> / <dfm 1> ;
Note that we could avoid using the default font multipliers (<dfm>) and instead put an individual multiplier in each frame that we wanted larger with <fm>, pick your poison, sometimes you have lots of frames and it's tedious to multiply them all individually and it's better to use <dfm> and other times <fm> is the clear winner, this one's a borderline case. Using <fm> instead of <dfm> our item might look like this:
-401 <xyjustification center> <msfd 1500> "++" / <fm 5> "1" <xy .25, .25> , <fm 5> "2" <xy .75, .25> , <fm 5> "3" <xy .25, .75> , <fm 5> "4" <xy .75, .75> , <msfd 100> * <fm 5> "5" <xy .5, .5> / ;
And last but not least we've specified the duration of the center frame as 100 ms with <msfd 100> as when you're merging frames together the duration must go in the last frame of the sequence because the comma frame separator is exactly equivalent to <msfd 0> / <noerase> so that <msfd 0> is going to override any duration you supply because your frame duration will be occurring before the frame separator and will be overwritten with zero.
And if you're interested in how that Hello World display is coded here it is:
0 <xyjustification center> "Hello World", "Hit the space bar to continue..." @2,
"مرحبا بالعالم" <xy .125, .125>, "Chào thế giới" <xy .375, .125>,
"नमस्ते दुनिया" <xy .625, .125>, "Привет, мир" <xy .875, .125>,
"ওহে বিশ্ব" <xy .125, .250>, "ಹಲೋ ವರ್ಲ್ಡ್" <xy .875, .250>,
"你好,世界" <xy .125, .375>, "سلام دنیا" <xy .875, .375>,
"សួស្តីពិភពលោក" <xy .125, .500>, "салам дүйнө" <xy .875, .500>,
"สวัสดีชาวโลก" <xy .125, .625>, "హలో వరల్డ్" <xy .875, .625>,
"هيلو ورلڊ" <xy .125, .750>, "ሰላም ልዑል" <xy .875, .750>,
"Γειά σου Κόσμε" <xy .125, .875>, "こんにちは世界" <xy .375, .875>,
"שלום עולם" <xy .625, .875>, "안녕하세요 월드" <xy .875, .875>;
The big thing with using other alphabets is that while a lot of them are what is referred to as a double byte character set you as user are generally unaware of when a given alphabet is going to require Unicode characters (either exclusively or in part) until you either use the Syntax Check (that will warn you about the presence of Unicode) or when you run the item file and Unicode characters are rendered as ?. Here you need to check the Unicode option check box and you should be good. And should you be using cursive Arabic fonts and they're disjointed WordPad is required, there's code to recognize that fonts like Calibri and Calibri (Arabic) are the same font, but who knows what Word does in the future...
Speaking of positioning things, when using instructions you could also use <xy> instead of <line> and feed it some screen coordinates if you didn't want to use line based positioning, but you'd also want to activate <xyjustification center> otherwise you'd have to calculate offsets from the coordinates of the top left corner of that text -- more than a little challenging when you also want that centered but of course the positioning of <inst> is itself dependent on <xyjustification lefttop> (or leftbottom if you have different sized fonts in there like I do in these instructions) so you have to turn on <xyjustification framebyframe> to allow it to be set on a frame by frame basis instead of it's default item by item setting.
So let's say we wanted instructions with an instructional image in the middle of the screen with text above and below it (I'll be using a Unicode character as I can't use images in the introduction but you get the idea). Here we'd have to use a bunch of new lines between our text segments and some guestimation as there's no other way to get <inst> aligned frames positioned (you can't have two <inst> keywords in a single item with different bounding boxes).
One thing to bear in mind here is that if you put the frame that uses <xyjustification center> in the middle of the item like I do you have to invoke <xyjustification lefttop> before the following text so <inst> positioning resumes correctly (<inst> with centered justification is very bad).
0 <xyjustification framebyframe> <xyjustification lefttop> <inst .1, .1, .92 hardmargin>
"Mel ", "ut ", "nonumy ", "deleniti ", "suavitate, ", "vix ", "id ", "utinam ", "eirmod ", "reformidans. ", "Corpora ", "consetetur ", "mei ", "ne, ", "ut ", "ferri ", "justo ", "eam. ", "Cum ", "cu ", "stet ", "recusabo, ", "sit ", "utroque ", "tractatos ", "percipitur ", "ut, ", "ad ", "usu ", "partem ", "habemus. ", "Assentior ", "percipitur ", "ut ", "vim. ", "Ad ", "admodum ", "accusata ", "eos. ", "Vero ", "integre ", "his ", "ad, ", "agam ", "facilis ", "eam ", "ut. ", "Ius ", "ut ", "feugiat ", "alienum ", "menandri, ", "ut ", "sit ", "bonorum ", "dolorem ", "patrioque, ", "detracto ", "praesent ", "dignissim ", "ad ", "quo." <inst nl>, <inst nl>, <inst nl>, <inst nl>, <inst nl>, <inst nl>, <inst nl>,
<inst esc> <fm 5> "Ω" <xyjustification center> <xy .5,.5>,
<xyjustification lefttop> "Esse ", "urbanitas ", "cu ", "sed, ", "scaevola ", "partiendo ", "necessitatibus ", "pro ", "an. ", "Duo ", "at ", "tollit ", "eripuit ", "facilisi. ", "Eum ", "sonet ", "hendrerit ", "democritum ", "an, ", "ad ", "affert ", "aeterno ", "cum. ", "Urbanitas ", "definitiones ", "ei ", "ius.",
<inst esc> "Hit the space bar to continue..." @12;
1 of N Responses Section
While DMDX's standard mode is a binary response paradigm it is easy enough to extend to 1 of N being correct (usually it's 1 of N but there can just as easily be more than one correct response) and doing such a task involves mapping and unmapping signals for each different response to be gathered. While this is a little tedious it is nonetheless eminently doable. So let's say our task is to identify the missing number in a grid of four (of five) rapidly presented numbers from the previous example by pressing one of the 1 through 5 keys:
+501 <xyjustification center> <msfd 1500> "++" <dfm 5> <umpr> <umnr> <mnr +#2> <mnr +#3> <mnr +#4> <mnr +#5> <mpr +#6> / "1" <xy .25, .25>, "2" <xy .75, .25>, "3" <xy .25, .75>, "4" <xy .75, .75> * / <dfm 1>;
First thing that's different from our previous item is that these items are always going to have a + CR indicator, we're coding the correctness directly in the item which brings up the second difference, it unmaps the previous positive response keys and the negative ones with <umpr> <umnr> as with each item in 1 of N tasks the correct key(s) will change (almost) every time. After that we map the five response keys to either the positive keys with <mpr> or the negative keys with <mnr>. Because we're using the international #keyboard device the 1 key is the #2 button (you can find out what number a key is in TimeDX's Input Test) and because we're interested in the depression of the key rather than its release it's the +#2 signal. I also tossed a too long feedback specifier <tlfb No Response (keys are 1..5)> in there (not shown as it only needs to be in the item file once) to prompt people on the right keys to use...
2D Mouse Based Input Section
In order to demonstrate the many different things that DMDX can do this introduction has to chain from one item file to another with <chain> and <lastframe> because parameters set one way for one paradigm are often exclusive to other paradigms and the only way to get DMDX to use new or different parameters is to get it to load a new item file.
Normally users of DMDX don't have to worry about this as they choose one mode of operation and rarely need a single experiment to use more than one but here it's unavoidable and for most users transparent barring a small delay as the new item file is loaded as when using the Direct3D renderer found in Windows 8 and later OSes there's an option to preserve the display with <chain preservevm itemfile> however users of Windows 7 and earlier OSes will experience a momentary flicker of the display as DMDX switches back to the desktop display. So if you're using an earlier OS sorry about the flickering you're about to see, it'll happen several more times throughout this introduction.
Another example of one of many inputs is a Stroop test, at least one that's not using a vocal response anyway and while people have done Stroop tests before with four colored keys on the keyboard it's hampered by having to remember which key is which color. Which brings us to the <2Did> parameter that allows us to use the mouse for a point and click interface. Here we can present our Stroop probes and have the subject click on the correctly colored box.
First up we have changes to our parameter line and because we don't want those changes active for the whole introduction we've chained to another item file (with <chain>) to allow us to set up a <2Did> input device mapping for just this section:
<2Did mouse blue,.3,.7,.4,.82 green,.4,.7,.5,.82 red,.5,.7,.6,.82 yellow,.6,.7,.7,.82 clipcursor,.3,.7,.7,.82> <dwc 255,255,255> <dbc 0,0,0>
Here we've told DMDX we want to use the mouse so DMDX has turned the mouse cursor back on and we've defined four areas of the screen that will be our buttons to click on (coordinate order is left, top, right, bottom and here we've used fractions of the screen width and height where the top left corner of the screen is 0,0 and the bottom right is 1,1). We've also defined an area of the screen that will clip the cursor's movement to limit the amount of time people might be dragging the mouse around.
Because this item file will be manipulating color we've also changed the background color to black and the writing color to white with <dwc 255,255,255> <dbc 0,0,0> which brings up another special DMDX gotcha and that is the tender issue of <RTFcolorOverride>. Because the usual color of text in an RTF file is black and more specifically the color of DMDX's feedback is also black when you set the background black you can't see either of them, black on black being black... So one is forced to specify the writing color as well and once you've done that DMDX is ignoring the color of the text throughout and you're using the writing color keyword <wc> everywhere. If there was a logical solution (other than using a white background) I'd have used it, I like putting the color in the RTF file and not dealing with RGB triplets.
+802 <fm 2> * <wc 0,128,0> "blue" <umpr> <umnr>
<mnr +blue> <mpr +green> <mnr +red> <mnr +yellow>,
<dfm 4 STAT> <xy .35,.75> <wc 0,0,255> "■", <xy .45,.75> <wc 0,128,0> "■", <xy .55,.75> <wc 255,0,0> "■", <xy .65,.75> <wc 255,255,0> "■" <dfm 1>;
So here we're displaying the bold "blue" frame in green with <wc 0,128,0> twice it's usual size with <fm 2> and then doing the same sort of mapping thing done in the previous example with <mpr> and <mnr> only this time we're using the names we assigned regions of the screen to in the <2Did> parameter keyword on the previous page with the correct button being the green one.
After mapping our responses we're displaying four square box characters (gotta love Unicode, there's some wild stuff out there) colored the right way across the screen for the subject to click on that line up with the named regions we defined in our <2Did> parameter earlier. Last but not least we set the font multipliers back to 1 for the feedback. We use a <dfm STAT> to set the font multipliers for the four boxes so it takes effect immediately for the first blue box and we use the delayed <dfm> at the end so (a) the yellow box is the right size and (b) the feedback that follows will be the right size. There's also a <xyjustification center> in the setup for these items (not shown).
Following are four of these Stroop items, click on the colored box that's the color of the text. While this example may not be scientifically useful because of the delay in mouse positioning now that DMDX handles Windows Touch touch screen devices it actually stands a chance of being usable so I exported it using the windowstouch device and stuffed it in a file named "2Did windowstouch.rtf " in the demos:
https://psy1.psych.arizona.edu/~jforster/dmdx/demos.zip
I would note that in the initial <2Did> button definitions instead of having to guestimate the locations of the corners of the regions for the buttons one could use the new <StoreCoords> and <SetButtonRect> keywords to automate the process. Your <2Did> parameter would have to be done with dummy coordinates and then what you would have to do is display the targets in black on black momentarily and in each frame use <sc> with four macros to remember the dimensions of the target (those macros would have to be defined before being used by <sc> with m.bl.++ for example, macros are covered later in this introduction):
0 <delay 2> <dfm 4 STAT> <dwc 0,0,0 STAT> <xy .35,.75> <sc .bl., .bt., .br., .bb.> "■", <xy .45,.75> <sc .gl., .gt., .gr., .gb.> "■", <xy .55,.75> <sc .rl., .rt., .rr., .rb.> "■", <xy .65,.75> <sc .yl., .yt., .yr., .yb.> "■" <dwc 255,255,255> <dfm 1> <continue>;
After that item has been displayed you'd then pass those values to <SetButtonRect> keywords to redefine the locations of the targets:
~1 <sbr +blue, ~.bl., ~.bt., ~.br., ~.bb.> <sbr +green, ~.gl., ~.gt., ~.gr., ~.gb.> <sbr +red, ~.rl., ~.rt., ~.rr., ~.rb.> <sbr +yellow, ~.yl., ~.yt., ~.yr., ~.yb.>;
I tend to prefer my guestimated values as the hit box on those targets goes quite a bit higher and lower than the glyph but maybe you're using graphics for targets and then YMMV. Also note that if your targets are graphics and thus not easily rendered as black on black you could display them for zero ticks and have them followed by a regular blank frame and they wouldn't be seen:
0 <delay 2> <dfm 4 STAT> <xy .35,.75> <sc .bl., .bt., .br., .bb.> "■", <xy .45,.75> <sc .gl., .gt., .gr., .gb.> "■", <xy .55,.75> <sc .rl., .rt., .rr., .rb.> "■", <xy .65,.75> <sc .yl., .yt., .yr., .yb.> "■" %0 / <dfm 1> <continue>;
Sentence Reading Section
While other more brute force methods to present sentence reading tasks in DMDX exist they are greatly facilitated by the progressive X keyword <px> where you give it an initial X coordinate to start at and the first word of the sentence and then following items present the rest of the words with the continue progressive X keyword <cpx>:
+701 <nfb> <px .2> * "Hickory ";
-702 <delay 2> <cpx> * ! "dickory ";
+703 <delay 2> <cpx> * ! "dock, ";
+704 <delay 2> <cpx> * ! "the ";
+705 <delay 2> <cpx> * ! "mouse ";
+706 <delay 2> <cpx> * ! "ran ";
+707 <delay 2> <cpx> * ! "up ";
+708 <delay 2> <cpx> * ! "the ";
+709 <delay 2> <cpx> * ! "clock ";
First up we're using <nfb> to turn the feedback off as we don't want it interrupting our sentence reading task. After that we're using <px .2> to start our sentence off one fifth of the way in from the left margin of the screen (centering being antithetical to the whole sentence reading paradigm).
Another new technique being used here is using <delay 2> to make the next word pop onto the screen almost immediately as opposed to taking the default 48 tick (or 800 ms) delay to do so. Note we don't want to use <delay 0> as that will produce display errors as there's no way DMDX can have something up at the same time as it's parsing it. Also we've used the legacy <NoErase> switch ! to stop the items erasing what was already on the screen. And as version 6.3.4.2 of DMDX if you're using a right to left font there's a right2left option (so <px .8 right2left>) or if you've already invoked <inst right2left> then <px> will observe that.
Here our task is to identify whether the last word presented is a real word or not.
Sentence Reading Using Branching
Of course in our first Sentence Reading example we've got feedback turned off because it would screw up our display when it erases the screen and even though one can in fact use the feedback only clears behind keyword <fbocb> and in addition restrict it to wrong feedback only with <wfbo> you'd find that feedback when displayed screws up the progressive X coordinate tracking… What you'd have to do is use <x> instead of <px> and <cpx> and re-present the whole sentence each frame and we're not going there because even if you do all of that you're still left with the situation where once an incorrect response has been given the rest of the sentence is immaterial.
Instead we'll use some branching to jump out of our sentence block and provide a bit of feedback only on mistakes. Branching is the evil nemesis of structured programming, the dreaded GOTO statement, however DMDX has to be able to scramble item files which would make structured programming quite a stretch and it's not like DMDX's item numbers are going away either and then there's the fact that tasks in DMDX also tend to be rather simple (things like the Corsi Block Tapping Task notwithstanding). When DMDX takes a branch after the item has finished it stops displaying or executing items till it finds an item with the matching destination item number in the branch. If it gets to the end of the file it will use the first item from the start of the item file that matches the branch destination number, if the destination item number in the branch specification is negative it will similarly just branch to the first matching item number unless the branch backwards to most recent option <bb2mr> is in effect where backwards branches always go to the most recent occurrence of the item number.
+701 <nfb> <px .2> * "Hickory " <biw 2>;
-702 <delay 2> <cpx> * ! "dickory " <biw 2>;
+703 <delay 2> <cpx> * ! "dock, " <biw 2>;
+704 <delay 2> <cpx> * ! "the " <biw 2>;
+705 <delay 2> <cpx> * ! "mouse " <biw 2>;
+706 <delay 2> <cpx> * ! "ran " <biw 2>;
+707 <delay 2> <cpx> * ! "up " <biw 2>;
+708 <delay 2> <cpx> * ! "the " <biw 2>;
+709 <delay 2> <cpx> * ! "clock " <biw 2>;
~1 <bu 3>;
2 <delay 2> "- - - W r o n g - - -" <msfd 533> <line 2> / ;
~3;
So here we've added a branch if wrong to item 2 <biw 2> to each item and there's three items at the end of our group of items that presents this sentence that can be duplicated for each sentence group that handle the wrong response from the previous items. If the subject successfully responds to each item DMDX would fall through to item 1 and we don't want it to spew our Wrong message so it skips over item 2 to item 3 with the branch unconditionally <bu 3> keyword. Item 3 is just a dummy target for our jump (if we didn't use a dummy item we'd have to know what the item number of the first item in the next sentence group is and if we're scrambling items that's impossible). Both items 1 and 3 don't display anything and execute with no delay as they use the skip display CR indicator ~. Otherwise a wrong response branches to item 2 and it has the same sort of look and feel of the usual wrong feedback that's displayed for 533 ms (the native feedback duration, on a 60 Hz display anyway).
Simple Counter Section
Another somewhat common thing to want to do is to have some sort of time limit or other sort of condition that needs checking every item. In order to perform these sorts of tasks your item file has to have some mechanism to store data (like the time to stop) and to do that DMDX has counters. Originally counter references in DMDX were limited to certain keywords and were just numbered so you could have things like <inc 1> that would increment counter number 1, then once the expression evaluator was added in the <SetCounter> keyword numeric references were no longer adequate as numbers in expressions were already constants so counter references there were referenced with a "c" or "counter" in front of them (so <set c1=c1+1>) and this was eventually back ported to the various keywords that referenced numeric counters (so <inc counter1>) and then after many years I added named counters (largely to match named macros that we'll get into later in the Macro section) where dot delimited names can be used after the "c" or "counter" prefix (so <set c.one. = c.one. + 1>, names are 40 characters maximum including the periods and they are case insensitive as are most things not in quotes in DMDX).
Note that if a named counter has been used a negative counter number will be automatically assigned (starting from -1) for internal uses where the name can't be used, the DMDX user however should not need to ever use it.
So in addition to a counter to monitor our condition to terminate our run we use a subroutine and a call in every item to that subroutine to check the condition. In order to have a time limit for the total item file we'll make use of a special token in DMDX's expression evaluator that lets us see what the current millisecond time is and stop the item file if we go beyond a time out based on that:
0 "Limited total time." <bu 840>;
~810 <bi 830, c.timelimit. .lt. millisectime>;
~1 <return>;
830 <lastframe> "Time's up.";
~840 <set c.timelimit. = millisectime + 10000>;
+8100 * "target" / <call -810>;
+8110 * "target" / <call -810>;
+8120 * "target" / <call -810>;
+8130 * "target" / <call -810>;
0 "Finished under time limit.";
First off we have an unconditional branch over our subroutine with <bu 840> so it doesn't get executed as the item file commences, if we didn't do that we'd get an error about there being a return without a corresponding call. DMDX will then skip over our subroutine and execute item 840 that sets the .timelimit. counter to the time that's 10 seconds in the future which we'll check the time against in the subroutine. After that each item then gathers a response and then calls item 810 that does the check and if we somehow manage to rip through those four items in under 10 seconds we'll get the finished under the time limit message.
~810 <bi 830, c.timelimit. .lt. millisectime>;
~1 <return>;
830 <lastframe> "Time's up.";
Our actual subroutine's items all begin with tilde skip display CR indicators ~ so that they execute with almost no delay (microseconds on modern machines) with the exception of item 830 where we actually want DMDX to display something. While we're getting a little ahead of ourselves in this introduction by using the branch if keyword <bi> here (it's covered in more detail later in the More Complex Counter section) I'm in a chicken and egg situation here where I need a more sophisticated feature in order to show off a simpler one so please excuse the hand waving... Item 810 starts off by checking if the remembered time in the .timelimit. counter from the start of the task's execution in item 840 is less than the current time (ie we've gone too long) and if so branches to item 830 otherwise it falls through to the next item that returns control back to the item after whichever one it was that called the subroutine at 810. Item 830 then has the <lastframe> keyword in it that makes DMDX stop execution after displaying the time's up message.
I would note that for the purposes of our demonstration I won't actually be using the <lastframe> option as we want the rest of the introduction to execute and indeed have to go to some lengths to unwind the stack but that's beyond the scope of our subject…
Limited total time.
Finished under time limit.
Alternatively a recent request was to stop an item file if a subject has four wrong responses in a row. Here instead of a counter storing the time we should stop at we'll use a counter to count the number of wrong responses in a row, setting it back to zero on a correct response and stopping when it gets over our threshold:
0 "Limited wrong responses in a row." <set c.wronginarow. = 0> <bu 940>;
~910 <biw 920>;
~1 <set c.wronginarow. = 0> <return>;
~920 <inc c.wronginarow.> <bi 930, c.wronginarow. .gt. 3>;
~1 <return>;
930 <lastframe> "Too many errors.";
~940;
+8200 * "target 1" / <call -910>;
+8210 * "target 2" / <call -910>;
+8220 * "target 3" / <call -910>;
+8230 * "target 4" / <call -910>;
+8240 * "target 5" / <call -910>;
+8250 * "target 6" / <call -910>;
+8260 * "target 7" / <call -910>;
+8270 * "target 8" / <call -910>;
0 "Not too many errors.";
As you can see, the only substantial difference from the previous example is in the decision making subroutine (we do set our count of wrong responses to zero at the start with <set c.wronginarow. = 0> as all counters must be initialized before any use otherwise an error will be thrown).
~910 <biw 920>;
~1 <set c.wronginarow. = 0> <return>;
~920 <inc c.wronginarow.> <bi 930, c.wronginarow. .gt. 3>;
~1 <return>;
930 <lastframe> "Too many errors.";
To implement our task the first thing our subroutine is going to do is a branch if the subject's response was wrong to item 920 with <biw 920> because we want to do different things based on the correctness of the response. If the response was in fact correct that branch won't get taken and DMDX will fall through to the following item that will set our count of wrong responses back to zero with <set c.wronginarow. = 0> and then return to the response gathering items. Otherwise when the response is wrong the .wronginarow. counter gets incremented with <inc c.wronginarow.> and then we test whether it's more than 3 and branch to item 930 if so with <bi 930, c.wronginarow. .gt. 3>. Otherwise we fall through to a <return> back to the response gathering items. Lastly we have item 930 catching too many wrong responses in a row that stops execution after tossing the appropriate message.
I would note that item 920 has one of those rare frames where the order of the components of a frame matter, if that <inc c.wronginarow.> occurred after the <bi 930, c.wronginarow. .gt. 3> then you'd have to make five wrong responses in a row before the experiment would be halted.
Limited wrong responses in a row.
Not too many errors.
Rating Section
Usually rating tasks are best done with <zil> <zor> <zol> item files as when DMDX is generating a .ZIL data file it automatically emits the name of the key that was struck and given that this item file is not a <zil> file but is instead an <azk> item file we've chained out to yet another introduction item file with the aforementioned parameters: <zil> for zillions of responses (usually recorded for the entire subject time out interval), <zor> for just one zillion response (making it more like an <azk> item file that proceeds once a response is received) and <zol> to make all the zillion output take only one line per item instead of a number of them (makes it easier to import into Excel for instance). A typical line of output in a .ZIL file generated with an item file that has <zil> <zor> <zol> in it's parameters might look like this (where the 3 key was hit):
Item 601, 3302.43 3302.43,+#4
Versus a typical line of an .AZK data file:
601 3302.43
I would note that importing .AZK or <zil> <zor> <zol> .ZIL data files into Excel is greatly facilitated by running them through AZK2CSV in the DMDX utilities available here:
https://psy1.psych.arizona.edu/~jforster/dmdx/dmdxutils.zip
All up our .ZIL rating item file looks something like this:
<ep> <zil> <zor> <zol> <id #keyboard> <vm desktop> <cr>
<msfd 166> <dwc 255,255,96> <dbc 32,64,128>
<vzk +#2> <vzk +#3> <vzk +#4> <vzk +#5> <vzk +#6> <eop>
0 "Some instructions" <t 15000> <clfb> <umnr> <umpr> <mpr +#2> <mpr +#3> <mpr +#4> <mpr +#5> <mpr +#6> ;
+601 <inst .1, .1, .92 hardmargin> "Lorem ", "ipsum ", "dolor ", "sit ", "amet, ", "ne ", "ubique ", "ridens ", "voluptaria ", "his, ", "in ", "augue ", "debet ", "facilisi ", "duo. ", "Oratio ", "assentior ", "sea ", "no, ", "eu ", "docendi ", "fastidii ", "iudicabit ", "mei. ", "Suas ", "rationibus ", "efficiendi ", "in ", "mei. ", "Vel ", "sonet ", "elitr ", "deleniti ", "an.",
<inst esc> "Please rate the text on the scale below:",
<inst esc> "1----2----3----4----5" <line 3>,
<inst esc> "dislike neutral like " <line 4> * ;
Another feature of running DMDX in zillions of responses mode is any signal from the mapped input devices will be emitted into the output which is not necessarily what one wants so in addition to the <zil> <zor> <zol> parameters one should also validate the keys one intends to use:
<vzk +#2> <vzk +#3> <vzk +#4> <vzk +#5> <vzk +#6>
Here we're validating the main keyboard keys 1 through 5 that are confusingly numbered 2 through 6, thanks IBM. Other than validating zillion response keys that's about it for special setup with <zil> <zor> <zol> rating files. After that we might have some instructions that also sets up conditions likely to be the same for all our rating items:
0 "Some instructions" <t 15000> <clfb> <umnr> <umpr> <mpr +#2> <mpr +#3> <mpr +#4> <mpr +#5> <mpr +#6> ;
First up we're setting the time limit for responses to 15 seconds with <t 15000> as rating tasks rarely need to be done in the default 4 second time out. After that we're making the feedback clear with <clfb> as all our responses are positive ones so we don't want the regular correct/incorrect feedback logic but we don't want to just suppress feedback completely with <nfb> as without some break between items it can be unobvious that a new rating has begun.
And after we set the feedback up we unmap the default negative response keys with <umnr> as well as the positive response ones with <umpr> and then we map our five keys as positive response keys with five <mpr> keywords.
For our task we'll have people rate whether they like a passage of text and display a Likert scale:
+601 <inst .1, .1, .92 hardmargin> "Lorem ", "ipsum ", "dolor ", "sit ", "amet, ", "ne ", "ubique ", "ridens ", "voluptaria ", "his, ", "in ", "augue ", "debet ", "facilisi ", "duo. ", "Oratio ", "assentior ", "sea ", "no, ", "eu ", "docendi ", "fastidii ", "iudicabit ", "mei. ", "Suas ", "rationibus ", "efficiendi ", "in ", "mei. ", "Vel ", "sonet ", "elitr ", "deleniti ", "an.",
<inst esc> "Please rate the text on the scale below:",
<inst esc> "1----2----3----4----5" <line 3>,
<inst esc> "dislike neutral like " <line 4> * ;
Beyond the slight tweak of using an <inst> keyword in a data gathering item pretty much everything here has been shown in this introduction before.
AZK Rating Section
Well simple ZIL rating tasks are a little flat, to liven it up a bit we can toss a bit of animation in there for a feedback feel however doing so means adding subroutines and using a multiway call on the key struck which is 90% of the work to do ratings in an <azk> file so we might as well do that.
<ep> <id #keyboard> <vm desktop> <cr>
<msfd 166> <dwc 255,255,96> <dbc 32,64,128> <eop>
~1 <msdfd 533> <bu 1>;
9101 <delay 2> <emit rated 1> "1--------------------" @3 / <return>;
9102 <delay 2> <emit rated 2> "-----2---------------" @3 / <return>;
9103 <delay 2> <emit rated 3> "----------3----------" @3 / <return>;
9104 <delay 2> <emit rated 4> "---------------4-----" @3 / <return>;
9105 <delay 2> <emit rated 5> "--------------------5" @3 / <return>;
9106 <delay 2> <emit no rating> "No Response" @3 / <return>;
~1;
0 "Some instructions" <t 15000> <nfb> <umnr> <umpr> <mpr +#2> <mpr +#3> <mpr +#4> <mpr +#5> <mpr +#6> ;
+601 <inst .1, .1, .92 hardmargin> "Lorem ", "ipsum ", "dolor ", "sit ", "amet, ", "ne ", "ubique ", "ridens ", "voluptaria ", "his, ", "in ", "augue ", "debet ", "facilisi ", "duo. ", ...
<inst esc> "Please rate the text on the scale below:",
<inst esc> "1----2----3----4----5" <line 3>,
<inst esc> "dislike neutral like " <line 4> *
<mwc +#2,-9101 +#3,-9102 +#4,-9103 +#5,-9104 +#6,-9105 cinr,-9106>;
So first up are our six stub subroutines that will be used to decipher which of the five positive responses was made when we prompt for a rating on our 5 point scale (the sixth is to handle no response being made):
~1 <msdfd 566> <bu 1>;
9101 <delay 2> <emit rated 1> "1--------------------" @3 / <return>;
9102 <delay 2> <emit rated 2> "-----2---------------" @3 / <return>;
9103 <delay 2> <emit rated 3> "----------3----------" @3 / <return>;
9104 <delay 2> <emit rated 4> "---------------4-----" @3 / <return>;
9105 <delay 2> <emit rated 5> "--------------------5" @3 / <return>;
9106 <delay 2> <emit no rating> "No Response" @3 / <return>;
~1;
You'll notice that the pair of item 1s is used to make DMDX branch over these stubs when it executes the item file as we only want these to be called from our rating item.
I also changed the default display duration to 533 ms (the usual feedback duration) with <msdfd 533> otherwise the feedback disappearing in the 166 ms of the default frame duration of this item file is a little ineffective.
9105 <delay 2> <emit rated 5> "--------------------5" @3 / <return>;
Because we're not gathering responses in these items similar to an instruction we've removed the + or - CR indicator. We've also tossed a <delay 2> in there so the feedback happens as quickly as possible after the rating keypress, without it it's a bit disjointed with the default delay in an item's display being 48 ticks or about 800 ms on most machines.
Next up because we're no longer generating a <zil> data file our item file will be responsible for distinguishing which of the five ratings were made and for that we emit the text relevant to a particular rating into the data file. A quick note about <emit> and that is you can't use something like <emit 1> for rating one as emit's primary purpose is to emit a counter's value so putting <emit 1> in there will generate an error complaining about the use of an uninitialized counter, oops...
Whereas the output .ZIL data file from our previous example will have data in it like this (assuming we responded 2 1 3):
Item 601, 2695.24 2695.24,+#3
Item 602, 320.48 320.48,+#2
Item 603, 364.44 364.44,+#4
Our .AZK data file will have data in it like this with our emitted data in comments (assuming we responded the same way):
611 1090.41
! rated 2
612 907.40
! rated 1
613 365.57
! rated 3
Back to our .AZK rating item file:
9105 <delay 2> <emit rated 5> "--------------------5" @3 / <return>;
Lastly we have a <return> that returns control to the item that follows the one that called that item.
After that there are instructions like the <zil> <zor> rating example that also sets some things up:
0 "Some instructions" <t 15000> <nfb> <umnr> <umpr> <mpr +#2> <mpr +#3> <mpr +#4> <mpr +#5> <mpr +#6> ;
Only difference here is we're turning feedback off this time with <nfb> because we'll be generating that ourselves.
+601 <inst .1, .1, .92 hardmargin> "Lorem ", "ipsum ", "dolor ", "sit ", "amet, ", "ne ", "ubique ", "ridens ", "voluptaria ", "his, ", "in ", "augue ", "debet ", "facilisi ", "duo. ", ...
<inst esc> "Please rate the text on the scale below:",
<inst esc> "1----2----3----4----5" <line 3>,
<inst esc> "dislike neutral like " <line 4> *
<mwc +#2,-9101 +#3,-9102 +#4,-9103 +#5,-9104 +#6,-9105 cinr,-9106>;
Similar to the <zil> <zor> rating task the only difference in our data gathering item is our multiway call <mwc> where we're feeding it signal names and item numbers it should call if that key was hit with the exception of the last pair that's using the call if no response keyword cinr to catch the last possible case. The item numbers are negative because we're telling DMDX to go look for those items before the item that's calling them, you could leave the sign out and DMDX would just take a little longer to process the call.
In addition to being able to use the mouse as a pointer in nominative tasks like the earlier Stroop demo DMDX can use the mouse as a continuous rating device should you wish to gather ratings on video clips as detailed in the <2did> help (google "dmdx 2Did") as well as demoed in dvRatingsDialdemo.rtf in the digital video demo:
https://psy1.psych.arizona.edu/~jforster/dmdx/DVDEMO.ZIP
Some rating tasks can require typed data, while DMDX can collect typed responses using a modification of the zillion input mode called zillion typed responses invoked with <ztr> for simple Roman characters using a .ZIL data file the more sophisticated <prose> is required should you need to use an .AZK data file. <Prose> is good for gathering multiple typed words, it should work in an international setting once the Unicode option is on and it includes the ability to back space and edit text across multiple lines. I won't get into <prose> here as it's detailed in the help (google "dmdx prose") and I also use it in the more advanced remote testing commstest4 communications test if you want to see how well it handles your locality at:
https://psy1.psych.arizona.edu/~jforster/dmdx/commstest4.exe
Instead for a typing demo <ztr> offers us the occasion to get a little bit deeper into counters and expressions which we'll do in the next section.
More Complex Counter Section
For a typing task free from international non-roman character set considerations it occurred to me that I could demo the Subtract 7 task I set up years ago where the idea is to stress a subject out by making them have to subtract 7 from a given number repeatedly. Here we can use the zillion typed response <ztr> mode and only worry about numeric keys (be they on the main keyboard or the numeric keypad) and use some counters and branching for a nice introduction to procedural item files as well.
Usually one would be validating keys for a <zil> job but <ztr> is a little special in that it validates the roman characters and numeric keys as well as the numeric keypad keys on both the keyboard device and more relevant to us, the #keyboard international device as well so we don't need to worry about <vzk> -- unless of course we have to invalidate the roman keys because we only want numeric keys available, however I'm not going there as that's at least 24 keys of invalidating and it would be easier just to invalidate all the keys with <izk> and then validate just the numeric ones and the Subtract 7 task is supposed to be a stressor anyway so fat fingering stuff can just cause a bit more stress…
So the additions to our parameter line are <zil> <ztr> <ntl> <fbocb> <zekn +#156> <zekn +#28> where <zil> gets us the zillion response mode, <ztr> gets us the zillion typing mode, <ntl> turns on DMDX's no time limit mode (a little more elegant that just setting the time limit to a really big value), <fbocb> that stops feedback from clearing the whole screen and has it only clearing behind the feedback when it's active (not that we're using feedback per se in the classic DMDX sense however <ztr> uses the feedback mechanism to display the typed characters) so our subtract seven prompt can remain on the screen and lastly <zekn +#156> <zekn +#28> that allows people to use the numeric keypad enter key as well as the main one if they so desire.
Speaking of things staying on the screen I will point out that our Subtract 7 task only keeps it's prompt on the screen for a single iteration or until an error is made (so they have to keep the number to subtract 7 from their mind, a much more difficult and stressful task) which makes it's structure a little more complicated as we'll need one loop that prompts the subject and keeps prompting them if they get it wrong and another that doesn't prompt them once they get it right and keeps on not prompting them till they get it wrong again.
0 "Subtract 7" <set c.target. = 777 + (random 223)> <mpr +#28> <mpr +#156>;
~1 <set c.timeout. = millisectime + 30000>;
~100 <bi 999, c.target. .lt. 7>;
~1 <bi 999, millisectime .gt. c.timeout.>;
+101 * "Subtract 7 from " <appendcounter c.target.>;
~1 <set c.target. = c.target. - 7> <bi 200, c.target. .eq. atoizilliontext>;
~102 ;
0 <delay 2> <ln -1> "Wrong." , "The correct answer is " <apc c.target.> <bu 100>;
~200 <bi 999, c.target. .lt. 7>;
~1 <bi 999, millisectime .gt. c.timeout.>;
+201 *;
~1 <set c.target. = c.target. - 7> <bi 102, c.target. .ne. atoizilliontext>;
~1 <bi 200, lastrt .le. 4000>;
1 <msfd 1000> "Please try and respond quicker" / <bu 200>;
999 "Time's up!";
So here for the first time our DMDX item file is starting to look like a real program as all those items with the tilde skip display CR indicator are tests and branches implementing our task.
0 "Subtract 7" <set c.target. = 777 + (random 223)> <mpr +#28> <mpr +#156>;
In our initial instruction we're setting the .target. counter to 777 plus some random number between zero and 222 so that each subject gets to do a slightly different task or (God forbid) they have to do it a second time. This counter is going to be the target number they have to type in for each iteration of our task. We've also added the enter keys as a positive responses so we can see how long they were typing for and ask them to speed it up if they're too slow.
~1 <set c.timeout. = millisectime + 30000>;
In our first purely procedural item we're setting a second counter up to be the time limit in the task. As demonstrated previously expressions in DMDX have a number of special tokens, we'll be using a few here and the first one is the millisecond time at the time that expression was evaluated. Because I'm not a sadist I'm only exposing you to a 30 second task (from the time you dismiss the initial instruction), usually this would be a 5 minute task and that 30000 would be 300000.
~100 <bi 999, c.target. .lt. 7>;
~1 <bi 999, millisectime .gt. c.timeout.>;
Item 100 is the start of our bifurcated loop fetching numbers from the subject and there will be branches back to this item from later in the task. Next we have the branch if <bi> keyword where DMDX takes a target item number and an expression and if the expression is true (non-zero) at item parse time it will take the branch to that item number. While DMDX has a number of different branching keywords they were added before <bi> was and these days almost all of my general purpose branching is done with <bi> because it is so much more flexible -- for instance in addition to the usual arithmetic operators you have the logical operators .LT., .GT., .LE., .GE. (less than, greater than, less than or equal to and greater than or equal to), .EQ., .NE., (equal to and not equal to), .AND. (logical AND) and last but not least .OR. (logical OR, see the <SetCounter> documentation for full list of the operators and their associativity). However what the original branches lack in flexibility they make up for in utility because they are evaluated after the subject has a made a response making them very useful if you're going to have decisions made on the subject's response in every item as opposed to a subroutine, whereas here with <bi> it's evaluated at item parse time before the display and response. With a skip display CR indicator in a subroutine the difference is moot but without it unawareness of that difference can be maddening. For reference the original branches are branch if counter less than or equal <bicLE>, branch if counter greater than <bicGT>, branch if correct <bic>, branch if wrong <biw>, branch if no response <binr> and the branches on error rate <bierLE> and <bierGT>.
Back to our example at hand:
~100 <bi 999, c.target. .lt. 7>;
~1 <bi 999, millisectime .gt. c.timeout.>;
Here we're checking if (a) we've run out numbers for them to type by checking if the .target. counter is less than seven and (b) if time has expired and we should bail out of the task (item 999 being the end of it).
+101 * "Subtract 7 from " <appendcounter c.target.>;
Speaking of bifurcation this first part of our loop is the part that actually prompts the subject, the later part that starts at item 200 is the other half that doesn't prompt them.
Here we're turning the clock on with the legacy asterisk switch and gathering a response, it being a <zil> task all the keystrokes hit here will be saved into the data file but we don't care about that because we've got <ztr> turned on and are only interested in it's interpretation of those keystrokes (more on that in a bit). So item 101 has to prompt the subject for the number they're supposed to subtract seven from and it does that by using <appendcounter> and passing it the .target. counter and it will add whatever text it is that represents that number to the end of the text in the frame <appendcounter> occurs in.
<ztr> tasks present the typed text on the feedback line (by default line 2, if we wanted another line we could change it with the feedback line keyword <fbl N>) and because we've specified feedback only clear behind with <fbocb> earlier on it's not erasing our prompt displaying the number to subtract 7 from.
~1 <set c.target. = c.target. - 7> <bi 200, c.target. .eq. atoizilliontext>;
Following item 101 that gets the input (terminated by the enter key) we have another procedural item that subtracts seven from the .target. counter and then branches to the other half of our bifurcated loop (item 200) that won't prompt the subject if they got it right by testing whether the new value of the .target. counter is equal to the second of our special expression tokens we'll be using in this task, atoizilliontext which is as you can guess the numeric value of the most recently typed in <zil> input (atoi being a classic C routine that converts an ASCII string to an integer).
~102 ;
0 <delay 2> <ln -1> "Wrong." , "The correct answer is " <apc c.target.> <bu 100>;
After that we have a stub item 102 that's a landing point for the second half of our bifurcated loop when the subject types the wrong number in. In addition if they got the number wrong in item 101 DMDX will fall through to this instruction as well informing the user they got it wrong and telling them the right number and then branching back to the first half of our bifurcated loop at item 100 that will prompt them for the next number in the series.
~200 <bi 999, c.target. .lt. 7>;
~1 <bi 999, millisectime .gt. c.timeout.>;
+201 *;
~1 <set c.target. = c.target. - 7> <bi 102, c.target. .ne. atoizilliontext>;
~1 <bi 200, lastrt .le. 4000>;
1 <msfd 1000> "Please try and respond quicker" / <bu 200>;
999 "Time's up!";
Lastly we have the second half of our bifurcated loop that has the same logic in it as the first half only it's the unprompted version of it that when they get it wrong branches back to item 102 in the first prompted half of the task. What is different is that we've got extra logic that checks the overall typing time. Because we've got the enter key mapped as a positive response it's the only response the standard DMDX response logic will see and so the lastrt token in an expression will have the total typing time in it and we can test if that's less than four seconds and if not flash up a respond quicker message for a second.
For your added convenience I've added logic to the task (not shown) that picks up you hitting the left shift key as a negative response to abort the task rather than make you sit through the whole 30 seconds unless you really want to.
Subtract 7
Aborted
There is one more keyword that's worth talking about at this point and that's the <BranchDiagnostics> keyword. While developing a branching item file figuring just what hit the fan when can be a bit of a problem, turning the branch diagnostics on takes much of the guesswork out of it -- not all of it but as much as I reasonably can. With this you can activate pretty extensive diagnostic spew in your data file (be it a .ZIL file or an .AZK one) and additionally the diagnostic data can be copied out of DMDX using the Copy button on the run dialog (it's also available in the diagnostics.txt file that's usually in your temporary directory) that also contains the DMDX dialog messages so you can actually see which item is executed (as long as it's not a skip display item). Whenever a branch is taken or considered or a counter or macro is modified a comment about it will be spewed. In addition when counters are referenced in an expression their value is also spewed.
I would note you probably want to turn the diagnostics off once you get the item file working, I usually leave the keyword in the file in case I ever have to turn them back on but just put a ! at the start of the keyword like this <!BranchDiagnostics> to make it a keyword comment -- or you could just turn branch diagnostics off with <BranchDiagnostics 0> (<BranchDiagnostics> is equivalent to <BranchDiagnostics 1> which applies to all keywords so <nfb 0> turns off no feedback for instance so feedback is no longer suppressed).
In the following displays I've included a run through this Subtract 7 task, where I've answered correctly within the time limit twice and again a third time however I take too long and it nags me to speed up and then I respond again where I've answered incorrectly so am prompted by item 101 to enter the last one which I get correct but then have been timed out for the end of the task. First up is the (somewhat edited) contents of diagnostics.txt, after that will be the .ZIL file.
DMDX is running in auto mode (automatically determined raster sync)
D3D Video Mode 1920,1080,24,59
Item File <introduction4.rtf>
EXPERIMENT READY
Preparation A 0.02ms, B 0.00ms
0 <BranchDiagnostics> "Subtract 7" <set c.target. = 777 + (random 223)> <mpr +#28> <mpr +#156>;
Counter .target. has value 984
Preparation A 6.12ms, B 6.85ms
~1 <set c.timeout. = millisectime + 30000>;
Counter .timeout. has value 45420
~100 <bi 999, c.target. .lt. 7>;
Counter .target. referenced, has value 984
BranchIf exp (0) to item 999 not taken
~1 <bi 999, millisectime .gt. c.timeout.>;
Counter .timeout. referenced, has value 45420
BranchIf exp (0) to item 999 not taken
+101 * "Subtract 7 from " <appendCounter .target.>;
The initial prompted input request
Item 101 RT 6286.32 -- Error Rate 0%
Preparation A 5.40ms, B 1.86ms
~1 <set c.target. = c.target. - 7> <bi 200, c.target. .eq. atoizilliontext>;
Counter .target. referenced, has value 984
Counter .target. has value 977
Counter .target. referenced, has value 977
BranchIf exp (1) to item 200 taken
~200 <bi 999, c.target. .lt. 7>;
Counter .target. referenced, has value 977
BranchIf exp (0) to item 999 not taken
~1 <bi 999, millisectime .gt. c.timeout.>;
Counter .timeout. referenced, has value 45420
BranchIf exp (0) to item 999 not taken
+201 *;
The first unprompted input request
Item 201 RT 3051.90 -- Error Rate 0%
Preparation A 1.45ms, B 0.21ms
~1 <set c.target. = c.target. - 7> <bi 102, c.target. .ne. atoizilliontext>;
Counter .target. referenced, has value 977
Counter .target. has value 970
Counter .target. referenced, has value 970
BranchIf exp (0) to item 102 not taken
~1 <bi 200, lastrt .le. 4000>;
BranchIf exp (1) to item 200 taken
Using buffered location for branch to item 200 after not finding it forwards
~200 <bi 999, c.target. .lt. 7>;
Counter .target. referenced, has value 970
BranchIf exp (0) to item 999 not taken
~1 <bi 999, millisectime .gt. c.timeout.>;
Counter .timeout. referenced, has value 45420
BranchIf exp (0) to item 999 not taken
+201 *;
The second unprompted input request that I take too long on
Item 201 RT 6723.35 -- Error Rate 0%
Preparation A 1.43ms, B 0.07ms
~1 <set c.target. = c.target. - 7> <bi 102, c.target. .ne. atoizilliontext>;
Counter .target. referenced, has value 970
Counter .target. has value 963
Counter .target. referenced, has value 963
BranchIf exp (0) to item 102 not taken
~1 <bi 200, lastrt .le. 4000>;
BranchIf exp (0) to item 200 not taken
1 <msfd 1000> "Please try and respond quicker" / <bu 200>;
Preparation A 1.44ms, B 1.03ms
Unconditional branch to item 200
Using buffered location for branch to item 200 after not finding it forwards
~200 <bi 999, c.target. .lt. 7>;
Counter .target. referenced, has value 963
BranchIf exp (0) to item 999 not taken
~1 <bi 999, millisectime .gt. c.timeout.>;
Counter .timeout. referenced, has value 45420
BranchIf exp (0) to item 999 not taken
+201 *;
The third unprompted input request that I get wrong
Item 201 RT 5707.61 -- Error Rate 0%
Preparation A 1.42ms, B 0.03ms
~1 <set c.target. = c.target. - 7> <bi 102, c.target. .ne. atoizilliontext>;
Counter .target. referenced, has value 963
Counter .target. has value 956
Counter .target. referenced, has value 956
BranchIf exp (1) to item 102 taken
Using buffered location for branch to item 102 after not finding it forwards
~102 ;
0 <delay 2> <ln -1> "Wrong." , "The correct answer is " <appendCounter .target.> <bu 100>;
Preparation A 1.44ms, B 1.64ms
Unconditional branch to item 100
Using buffered location for branch to item 100 after not finding it forwards
~100 <bi 999, c.target. .lt. 7>;
Counter .target. referenced, has value 956
BranchIf exp (0) to item 999 not taken
~1 <bi 999, millisectime .gt. c.timeout.>;
Counter .timeout. referenced, has value 45420
BranchIf exp (0) to item 999 not taken
+101 * "Subtract 7 from " <appendCounter .target.>;
Having got the last one wrong we switch back to prompted input
Item 101 RT 3635.90 -- Error Rate 0%
Preparation A 4.70ms, B 1.73ms
~1 <set c.target. = c.target. - 7> <bi 200, c.target. .eq. atoizilliontext>;
Counter .target. referenced, has value 956
Counter .target. has value 949
Counter .target. referenced, has value 949
BranchIf exp (1) to item 200 taken
~200 <bi 999, c.target. .lt. 7>;
Counter .target. referenced, has value 949
BranchIf exp (0) to item 999 not taken
~1 <bi 999, millisectime .gt. c.timeout.>;
And now we've taken more than 30 seconds so we bomb out
Counter .timeout. referenced, has value 45420
BranchIf exp (1) to item 999 taken
~999;
0 "Time's up!";
Preparation A 1.82ms, B 0.76ms
And here is the .ZIL data file from that run:
! DMDX is running in auto mode (automatically determined raster sync)
! D3D Video Mode 1920,1080,24,59
! Item File <D:\dx5\DMDX\introduction4.rtf>
! Counter .target. has value 984
! Counter .timeout. has value 45420
! Counter .target. referenced, has value 984
! BranchIf exp (0) to item 999 not taken
! Counter .timeout. referenced, has value 45420
! BranchIf exp (0) to item 999 not taken
Item 101, (977) 3504.37,+#73 4290.32,+#71 5530.36,+#71 6286.32,+#28
The initial prompted input request
! Counter .target. referenced, has value 984
! Counter .target. has value 977
! Counter .target. referenced, has value 977
! BranchIf exp (1) to item 200 taken
! Counter .target. referenced, has value 977
! BranchIf exp (0) to item 999 not taken
! Counter .timeout. referenced, has value 45420
! BranchIf exp (0) to item 999 not taken
Item 201, (970) 1557.88,+#73 1804.22,+#71 2163.89,+#82 3051.90,+#156
The first unprompted input request
! Counter .target. referenced, has value 977
! Counter .target. has value 970
! Counter .target. referenced, has value 970
! BranchIf exp (0) to item 102 not taken
! BranchIf exp (1) to item 200 taken
! Using buffered location for branch to item 200 after not finding it forwards
! Counter .target. referenced, has value 970
! BranchIf exp (0) to item 999 not taken
! Counter .timeout. referenced, has value 45420
! BranchIf exp (0) to item 999 not taken
Item 201, (963) 2337.26,+#73 3173.31,+#77 4185.27,+#81 6723.35,+#156
The second unprompted input request
! Counter .target. referenced, has value 970
! Counter .target. has value 963
! Counter .target. referenced, has value 963
! BranchIf exp (0) to item 102 not taken
! BranchIf exp (0) to item 200 not taken
! Unconditional branch to item 200
! Using buffered location for branch to item 200 after not finding it forwards
! Counter .target. referenced, has value 963
! BranchIf exp (0) to item 999 not taken
! Counter .timeout. referenced, has value 45420
! BranchIf exp (0) to item 999 not taken
Item 201, (955) 4309.27,+#73 4639.29,+#76 4793.29,+#76 5707.61,+#156
The third unprompted input request that I get wrong
! Counter .target. referenced, has value 963
! Counter .target. has value 956
! Counter .target. referenced, has value 956
! BranchIf exp (1) to item 102 taken
! Using buffered location for branch to item 102 after not finding it forwards
! Unconditional branch to item 100
! Using buffered location for branch to item 100 after not finding it forwards
! Counter .target. referenced, has value 956
! BranchIf exp (0) to item 999 not taken
! Counter .timeout. referenced, has value 45420
! BranchIf exp (0) to item 999 not taken
Item 101, (949) 1537.88,+#73 2017.82,+#75 2535.99,+#73 3635.90,+#156
Having got the last one wrong we switch back to prompted input
! Counter .target. referenced, has value 956
! Counter .target. has value 949
! Counter .target. referenced, has value 949
! BranchIf exp (1) to item 200 taken
! Counter .target. referenced, has value 949
! BranchIf exp (0) to item 999 not taken
! Counter .timeout. referenced, has value 45420
And now we've taken more than 30 seconds so we bomb out
! BranchIf exp (1) to item 999 taken
ISI & Delay Section
The setting of an ISI in DMDX is done with the <delay> parameter or the frame <delay> switch (they operate quite differently) and is another one of those areas that requires a little more skill than meets the eye largely on account of the way it's use has morphed over the years. Originally almost all jobs that DMDX ran were controlled by a foot pedal request where the subject has to request the next item before they can respond to it, nowadays almost everyone is using a continuously running item file and that complicates life because the time DMDX considers to be the end of an item from which it is going to schedule the next item is the time the request was made and if there is no request issued by a subject (as is the case in continuous run jobs) the time the last frame is processed or displayed is taken to be the end of the item, not the time the subject responded or was timed out at, nor is it the end of any playing audio, it's display based -- which facilitates rigid ISIs but complicates plain old instances where people want a delay from one item to the next other than the default (which is 48 ticks, about 800 ms on most machines).
So for people who just want a different item delay and are not interested in rigid ISI control and don't want display errors in every item where the subject responded a bit slower or what have you they have to override the rigidity of <delay> by specifying the negative value of whatever delay they'd like, so for a full second you might have <delay -60> in the parameters (delay is specified in ticks) or if you weren't assured of having a 60 Hz display you'd use the millisecond variant of the delay keyword with <msdelay -1000> for instance.
The next most common usage is where people just want DMDX to rip from one item to the next as rapidly as possible (as we have in the earlier Sentence Reading section). Here it depends on whether you want DMDX to always proceed rapidly from one item to the next or whether you only want some items to proceed rapidly.
In the first case where it's all items you use a delay parameter of zero which is in fact a special command equivalent to <delay -2> telling DMDX to finish up everything with the last item and schedule the next one two ticks later (you might get away with one tick but I'm betting every now and then you'd get a display error).
In the case where you only want some items to proceed as rapidly as is reasonable like the earlier Sentence Reading example you have to use the frame based version of <delay> and use it in the first frame of the item and feeding it two ticks (using it in other frames will more than likely result in all hell breaking loose as it overrides DMDX's default frame sequencing). Note the frame based version of delay does not take negative values like the parameter based version of it does, it's strictly rigid.
In the case where you want an honest to God rigid ISI the easy thing these days is to use the period modifier of the delay parameter where you specify the period in ticks or milliseconds depending on your preference with <msd period 2000> for instance for a 2 second ISI (making sure of course that your item's display and subject timeout etc. sum to less than the period). Note the period option is only available in the parameter based version of <delay>. If you didn't want to use period for some unfathomable reason you'd have to make sure the display lasted longer than any possible RT or audio activity, add one tick to that for the last frame and then and you'd have to subtract that from your desired period and then feed the result to <delay>. You can check out the Timing Notes in DMDX's help where I explained all those intricacies prior to adding the period option...
There is one last case where people want a continuously variable delay and seeing as that requires the use of macros it's covered in the Macro section below.
Abort Display Queue Section
While <AbortItemExpression> was primarily designed to cut short the playback of audio or digital video when a subject responds or a baby loses interest in a looking time study when <aie> is combined with <AbortDQPurge> the two keywords allow us to have a display sequence continue while a subject is deciding to respond and then have it stop once they do respond allowing us to have things like strength of response tasks where the longer a subject takes to respond the longer a line is drawn on the screen.
The thing with <AbortDQPurge> is that I don't trust it completely in that DMDX was designed with the explicit assumption that the display queue would always play out completely so I don't guarantee <AbortDQPurge> working in all circumstances and instead only use it with simple displays. If you should find yourself in a situation where you need an animated display and <AbortDQPurge> is blowing DMDX up you'll have to resort to using <ContinueClockOn> instead (see the next section).
So <aie> takes an expression and when it evaluates true (non-zero) the item will abort and in it's original use it would be examining looking time counters for whatever condition was deemed proper for an abort, with the wider use of <aie> however we needed a way to probe DMDX's internal state to be able to detect when a response was made so a new token was added to the expression evaluator, jobstatus, and the specific state we're interested in is 7 which indicates DMDX received a response (if you're interested in the other job states you can consult the <SetCounter> help but I've yet to find a need for any state other than 7).
Other than that we're going to use a strength of response paradigm where the longer a subject takes to respond the stronger their response is assumed to be, we present them with an image (here of course it'll be a special character because the introduction doesn't have access to graphics) and after presenting it for a few seconds it'll switch to a strength of response display during which a response is valid and then a relax screen.
Note that feedback is turned off with <nfb>, as it's use with <AbortDQPurge> is spotty at best as it's put on the display queue and likely to get purged, instead if you want to provide feedback you'd either have to have a subroutine to do so or given that this particular paradigm is correctness agnostic we use <sprintf> to display a counter that's been loaded with the lastRT token in the expression evaluator that along with a %d in the frame will allow you to display the RT:
<nfb> <msfd 500> <cr> <abortdqpurge> <aie jobstatus .eq. 7>
+201 <fm 5> "¥" %120 / * @-1 "weak strong" , "- " /
@-1 "weak strong" , "-- " /
@-1 "weak strong" , "---- " /
@-1 "weak strong" , "------ " /
@-1 "weak strong" , "-------- " /
@-1 "weak strong" , "---------- " /
@-1 "weak strong" , "------------ " /
@-1 "weak strong" , "-------------- " /
@-1 "weak strong" , "----------------" / !;
290 %60 "relax (%d)" <set c.rt. = lastrt> <sprintf c.rt.> /!;
Multiple Responses Section
Occasionally people require more than one response to a given stimulus and while the traditional zillion responses mode can be used to do so with <zil> instead of the default <azk> it's a rather awkward mode in that an arbitrary number responses are gathered for a fixed time interval and the alternative of just using two items to gather the second response means there's a period when DMDX isn't paying attention to any responses from the subject. Here <ContinueClockOn> is the solution where the first item uses a regular clock on and because DMDX automatically buffers signals between items the second item can pick them up with <cco> (a regular clock on just discards the buffered responses).
RTs with <cco> are measured from the time the last response was made or the last response was timed out at if there was no response instead of the time that the frame that contains the <cco> is displayed (as is the case with the usual clock on). This means that in order to determine the RT for the second response one has to store the first RT away using lastRT and sum the second RT to it to determine the second RT's time relative to the original clock on:
+1000 <nfb> <t 100000> "two response target" <co> ;
+1100 <set c1 = lastRT> ! <cco> ;
0 d2 <set c2 = lastRT> <set c3 = c1 + c2> "times %d + %d = %d" <sprintf c1, c2, c3>;
We're being careful to once again use <nfb> as any of the automated feedback mechanisms are not going to be good given our two item construction, we're also setting a stonking timeout (at the very least it would have to be twice the normal timeout and I suspect timeouts don't make a whole lot of sense in a two response paradigm anyway) and I tend to use <co>, the keyword version of *, when using <cco> just to make the usages more consistent. We're using an instruction for the following feedback item so it is dismissed with a request, not because one would be using it with subjects but instead to allow you to examine the relationship between the RTs. BTW, beyond the fact that we're using the classic numbered counters instead of the named counters used in previous examples in this introduction (so cN instead of c.name.) you'll notice we're passing three counters to <sprintf>, the limit is six.
Note that in item 1100 even though we're using lastRT in an item with <cco> in it because the expression is evaluated at item parse time before the display and gathering of responses it still contains the RT from the previous item.
Originally <cco> was introduced to handle a changing display during a subject's response window that would allow DMDX to bail out of the sequence once the subject responds and even though the introduction of <AbortDQpurge> (above) obviated that requirement there's some chance it won't work in all circumstances so the progressive response example is a nice one to show off <cco> without going whole hog like we do later on with the procedural stuff in the Dynamic Keyword Parameters sub-section of the Macro section (there the emphasis is elsewhere in any event and <cco> is merely a means to another end).
So similar to the sentence reading with branching example we're going to present displays and branch over the following ones if a response is detected, otherwise after about 4.5 seconds with a carefully chosen delay and frame duration to make each item take a half second the response will be timed out. The big difference comes from having to keep track of how many items we timed out, whereas with the <AbortDQpurge> example it was kept track of automatically as it was just a normal item terminated in an unusual fashion, here because our RT is only timed from the previous timed out RT and not from the initial clock on we have to sum all that timed out time if we're going to present an overall RT (originally no one did this with <cco> items, it's only now that I think wouldn't it be cute to do so that I find out just what's involved in doing so, ho hum).
So each <cco> item has <set c1 = c1 + lastRT> in it and while I could have used 500 instead of lastRT using lastRT is more portable as after timed out items lastRT will have the value 500 and if we change the timeout later on it will automatically handle it. Note that our frame duration is about one tick shy of 400 ms as the ! <bic 2290> frame takes a tick to display.
<nfb> <msfd 383> <msd 100> <timeout 500> <cr>
+2201 <umnr> <set c1 = 0> <fm 5> "¥" %120 / * @-1 "weak strong" , "- " / ! <bic 2290>;
+2202 <set c1 = c1 + lastRT> <cco> @-1 "weak strong" , "-- " / ! <bic 2290>;
+2203 <set c1 = c1 + lastRT> <cco> @-1 "weak strong" , "---- " / ! <bic 2290>;
+2204 <set c1 = c1 + lastRT> <cco> @-1 "weak strong" , "------ " / ! <bic 2290>;
+2205 <set c1 = c1 + lastRT> <cco> @-1 "weak strong" , "-------- " / ! <bic 2290>;
+2206 <set c1 = c1 + lastRT> <cco> @-1 "weak strong" , "---------- " / ! <bic 2290>;
+2207 <set c1 = c1 + lastRT> <cco> @-1 "weak strong" , "------------ " / ! <bic 2290>;
+2208 <set c1 = c1 + lastRT> <cco> @-1 "weak strong" , "-------------- " / ! <bic 2290>;
+2209 <set c1 = c1 + lastRT> <cco> @-1 "weak strong" , "----------------" / !;
2290 %60 "relax (%d)" <set c1 = c1 + lastrt> <sprintf c1> /!;
Note that we're unmapping the negative responses there with <umnr> so that our branch if correct <bic> can catch all likely responses, our actual code doesn't do that and instead accepts both responses and to do so I have a bit of fudgimification in there. I thought I could just use the = CR any response indicator but it fails and instead I had to use <mwb binr,1 bu,2290>; ~1; instead of <bic 2290>; to catch both positive and negative responses but not timed out ones. Digging into the code I see CR any includes timed out responses as well, guess that's what they wanted way back when, me I'd require an actual response myself. And no, you can't use <mwb biw,2290 bic,2290> because timed out responses are wrong...
Also note that this example was coded with a 60 Hz display in mind, that 383 ms value should really be tuned to the given refresh rate, however I haven't exposed a way for an item file to find out the refresh rate (not without excessive chicanery anyway) so we're going to live with it...
Macro Section
The final frontier of DMDX scripting is the use of macros. Here you can assign a text string to a token and have it expand at a later date in the item file with <macro token string>. The token comes in one of two forms, it's either a legacy single character (so <macro t string> for instance) or it's a dot delimited string itself that's 40 characters or less in length (so <macro .token. string>). To invoke the macro and have it expand to it's string value it's token is preceded by a back tick ` (so `t or `.token.). The string form of the name can include Unicode characters should your language not be English.
There are all sorts of limitations, confounds and commands that I'm not going to try and enumerate here, instead you can dig them out of the macro section in the help. For instance if you want to actually use back ticks in your items it's best to use two for every one as DMDX will collapse two of them to a single back tick rather than having the code decide your back tick isn't a macro expansion sequence. Another thing is that there are actually two varieties of the macro expansion lead in character, the back tick and the legacy tilde lead in where you can't define a macro in the same item that it's expanded in as the definition is parsed after the expansion is done when the item is read, something that's pissed me off for decades as I would forget about it and then have to go and rework my item file, grr. Because of said limitations I won't talk about the tilde lead in in the rest of this introduction.
Another point about macros is that you can't put a keyword in a keyword so if you're looking to have keywords expand in a macro you'll have to use the old legacy DMTG version of a macro definition and for that reason that tends to be the way I almost always use macros. The original DMTG switch based definition syntax is MtDstringD where t is the token and D is some arbitrary delimiter character that you choose that isn't in the string you want expanded (typically I'll use a plus sign). That token can be either a single character or the dot delimited version as in M.token.DstringD.
There are countless things macros are useful for, ranging from bypassing DMDX's limitations to iterating over counters, the sky's the limit and while I could add a ton of code to DMDX to parse for counters everywhere for the few instances that exist where it's actually needed using macros to get around the limitation is pretty painless these days. To demonstrate this the simplest thing is where you want to feed a keyword that doesn't take a counter for a parameter a variable value. So we're going to take the last ISI usage from above where we want to have a variable delay between items and in order to do this we will set a macro up that we can feed to <msdelay>:
+1 <macro set .jitter. = (random 4850) + 150> <msdelay `.jitter.> "variable ISI item 1 (was .`jitter.)" * ;
Our variable ISI demo is dramatically simplified by using our shiny new in place macro expansion, I feel better already having put all that effort into developing it. Here we're using one of the <macro> variant commands set that takes an expression and sets our macro to the string representing that value and then passing that to <msdelay> which will delay the start of that item's display by however many milliseconds our expression came up with (technically it delays that frame's display, lookout if you ever use a delay keyword in anything but the first frame of an item). Be aware that if you had used <delay> in the parameters (the first line of the item file) and hadn't made DMDX use lazy ISI scheduling (by specifying a negative delay) you'd be getting display errors when the subject is slow responding unless you had selected your ISI interval to never be less than the subject timeout plus some time for the feedback (usually a skidge more than a half second). If for some reason you had to do so you'd probably just want to have a blank frame at the start of your items instead of this variable ISI hack for which you could either use this setup feeding a <msfd> keyword instead of <msdelay> or you could use the set counter keyword <set> and feed that counter to a <msctrfd> keyword.
A slightly more sophisticated usage would be to use macros and scrambling to expand the earlier positioning example to randomly move the corner stimuli around and we're going to use shapes instead of numbers and the task will be to decide if the outlined character in the middle occurs in the corners.
The way we're going to do it is by having four macros for the corners and we're going to scramble the content items around them with a <scramble 1> in the parameters and some judicious dollar signs pinning down the items that need to be fixed in place.
$~1 mO+.tl.+;$
~1 m`O+ "■" <emit square> +;
$~1 mO+.tr.+;$
~1 m`O+ "●" <emit circle> +;
$~1 mO+.bl.+;$
~1 m`O+ "▲" <emit triangle> +;
$~1 mO+.br.+;$
~1 m`O+ "◆" <emit diamond> +;
$-1001 <msfd 1500> "++" <xy .5, .5> / <dfm 4 stat> `.tl. <xy .25, .25> , `.tr. <xy .75, .25> , `.bl. <xy .25, .75> , `.br. <xy .75, .75>, "☆" <xy .5, .5> * / <dfm 1> ;$
\
The item 1s surrounded by dollars are fixed blocks that set up which macro the following item will define by setting macro O to the token name for each corner (top left, with .tl. top right with .tr. etc) and the scramble shuffles those other items around them so that any given shape will wind up in any given corner. Additionally our corner macros also emit the name of the shape that went in that corner so the data file will have a record of which shape went where -- not so relevant for this example but the original this example came from has many more corner displays shuffled across many presentations, here we've got a back slash after each set so they don't intermix which is why we're going to have a multi-scramble demonstration following this...
While I could have picked a somewhat simpler example there's nothing like throwing people in the deep end so a bit of explication is probably in order and we'll consider the two items that set up the top left display:
~1 mO+.tl.+;
~1 m`O+ "■" <emit square> +;
The first item is relatively straight forward in that it sets macro O to be the characters .tl. so that the following item can select the shape for macro .tl. (after the scramble of course any one of the four shapes could wind up being in this item's place). The second item uses the expansion of the first item's macro O with `O to alter it's macro definition thus allowing any of the corner macros to pick up the square shape, kind of meta I admit but there you go.
While we could have used the keyword macro definition for macro O with <macro O .tl.> instead of mO+.tl.+ for instance we have to use the legacy switch macro definition to set the corner macros up with m`O+ "■" <emit square> + because they contain the angle bracket delimited emit keywords that would terminate our macro definition instead of being included in them (angle brackets don't nest, not in DMDX anyway and certainly not in macro definitions†).
-1001 <msfd 1500> "++" <xy .5, .5> / <dfm 4 stat> `.tl. <xy .25, .25> , `.tr. <xy .75, .25> , `.bl. <xy .25, .75> , `.br. <xy .75, .75>, "☆" <xy .5, .5> * / <dfm 1> ;
The potentially interesting feature of the test item's construction is that we're expanding macros that haven't explicitly been defined, in that nowhere in the definitions was there an explicit m.tl.+ "■" <emit square> + and instead the definition came about as a result of macro O's expansion.
And no, there is no rhyme or reason for my choice of using legacy single character macro tokens (macro O) and modern multi-character ones (.tl. for instance) other than showing that both are possible to use. Three identical copies of this item follow so you can see how the scrambling is changing our display.
†Although angle brackets in keywords have been fine for a number of years as long as they're in a quote delimited string which was done for the <send> keyword,
And if things start going south on you and you want to see what was done you'll have to look at the diagnostics.txt file (or use the new Copy button on the main DMDX dialog) as rtfparsed.itm and the other files like scrambled.itm will not reflect the macro operations as they're all processed prior to macro expansion. For instance here's a sample of the diagnostics from that item having executed:
+1001 <msfd 1500> "++" <xy .5, .5> / <dfm 4 stat> "■" <emit square> <xy .25, .25> , "★" <emit star> <xy .75, .25> , "●" <emit circle> <xy .25, .75> , "▲" <emit triangle> <xy .75, .75>, "☆" <xy .5, .5> * / <dfm 1> ;
square
star
circle
triangle
Item 1001 RT 703.31 -- Error Rate 0%
In the past sorting through the diagnostics could be a bit tricky however current versions of DMDX will provide a display very similar to the original item, the exception being long items where ellipses (...) can indicate that not all text was displayed.
Branch diagnostics also detail macro definitions and when in place macro expansion is in use they also provide extra diagnostics pre and post back tick expansion so turning them on with <BranchDiagnostics> is also a good idea.
So as noted our RT gathering item order is going to remain fixed and that's not a good thing so we enter the dreaded world of multi-scrambling where you can pass an item file through the scramble routines any number of times as long as you're smart enough to figure out how to get the later scramble parameters to stay where you want them after the earlier passes through scramble. We're going to want a first scramble that does everything our existing scramble does and then a second scramble with a grouping factor of 9 to change the relative order of the RT gathering items with these additions to the parameter line <mss 1,1> <mss 2,1> <msg 2,9>. First off we have <mss 1,1> that tells the multi-scramble routines that on iteration one the scramble block size should be one, then we have <mss 2,1> that similarly indicates that the second scramble should also have a block size of one and lastly we have <msg 2,9> that specifies the grouping factor for the second scramble should be nine.
We'll be replacing the dollar delimiters with <ms# 1> which means on iteration one of our multi-scramble those keywords will become $ symbols, likewise we'll be replacing the back slashes with <ms\ 1> as on iteration one we just want to mix up the corner resources to build the next 1000 series RT gathering item. Fortunately for us our second iteration of the multi-scramble is about as straight forward as multi-scrambling gets because it's just a grouping factor, no need for extra keywords to do "interesting" things, all we'd have to do is nest our instructions in pairs of fixed block delimiters like this to make sure they don't move in either iteration one or iteration two of the multi-scramble:
<ms# 1> <ms# 2> 0 "Instructions";<ms# 2> <ms# 1>
And this is what our macro controlled corner shuffling set of items winds up looking like with multi-scramble keywords in it:
<ms# 1> ~1 mO+.tl.+;<ms# 1>
~1 m`O+ "■" <emit square> +;
<ms# 1> ~1 mO+.tr.+;<ms# 1>
~1 m`O+ "●" <emit circle> +;
<ms# 1> ~1 mO+.bl.+;<ms# 1>
~1 m`O+ "▲" <emit triangle> +;
<ms# 1> ~1 mO+.br.+;<ms# 1>
~1 m`O+ "◆" <emit diamond> +;
<ms# 1> -1101 <msfd 1500> "++" <xy .5, .5> / <dfm 4 stat> `.tl. <xy .25, .25> , `.tr. <xy .75, .25> , `.bl. <xy .25, .75> , `.br. <xy .75, .75>, "☆" <xy .5, .5> * / <dfm 1> ;<ms# 1>
<ms\ 1>
Indexed Branching Section
Another thing that becomes possible with macros is selecting elements from a list for, say, a faux Ultimatum task (we're splitting random offers evenly), albeit with the use of indexed branching. Here one sets up a list of items that set a macro to some value and then use an indexed branch into that list to select just one element. The list can be scrambled if you need to select every one of them in random order or more simply a random number can be used to select one randomly with the caveat that not all elements are guaranteed to be used and that there's no control over how many times one of those elements may be reused.
In the original Ultimatum task all the offers were kept in a list and then each offer was selected from it after it was scrambled, that example is in the help (google "DMDX dynamic item content" and click the Ultimatum Game link) so we're not going to go into that here. Instead we'll present our simplified Ultimatum task that just keeps upping the tally till you decline an offer.
~1 <bu 6998>;
~6999 <macro set o = ((random 10) + 1) * 2> <set c1 = (random 4) + 6900> <ib c1>;
~6900 <macro m Bob> <return>;
~6901 <macro m Jim> <return>;
~6902 <macro m Sue> <return>;
~6903 <macro m Mary> <return>;
~6998 <clfb> <set c2 = 0> <call -6999>;
~1 <bu 6801>;
~6800 <set c2 = c2 + (`o / 2)> <call -6999>;
+6801 <line -5> "You have $" <apc c2>, <line -1> "`m offers $`o", <line 1> "Do you accept it?" * <bic -6800>;
In the heart of our faux Ultimatum task we have a subroutine to both come up with an offer amount and a name of a sham conspirator to offer it:
~6999 <macro set o = ((random 10) + 1) * 2> <set c1 = (random 4) + 6900> <ib c1>;
~6900 <macro m Bob> <return>;
~6901 <macro m Jim> <return>;
~6902 <macro m Sue> <return>;
~6903 <macro m Mary> <return>;
The first half of item 6999 sets macro O to the characters that represent an offer from $2 through $20 (I'm cheating by keeping offers even so then when the offer is split we don't have to deal with fractions of a dollar) with <macro set o = ((random 10) + 1) * 2>.† After that an indexed branch jumps to one of the next four items that set macro M to some name. An indexed branch takes a counter and branches to the item that matches that counter's value so the first of our name element items is 6900 so we take 6900 plus some random number between zero and three and that selects for one of our four name selection items with <set c1 = (random 4) + 6900>.‡ After that the indexed branch itself uses counter 1 as it's destination with <ib c1>.
†Note that function call association in DMDX's expression evaluator routines (Bob Brodt's CALC.C) is quite counterintuitive, while random returns a value between 0 and one less than it's parameter brackets are always associativity brackets, there's no function call variety of them and function call precedence is quite low so random(10) + 1 evaluates to a number ranging from 0 to 10 rather than the 1 to 10 that one might reasonably expect because the 10 is going to get added to the 1 before random gets invoked with a parameter of 11. You think that didn't throw me for a loop when I first ran into it.
‡Worse random(4) + 6900 would evaluate to a number between 0 and 6903 and not 6900 to 6903 almost instantly generating an item not found error.
~6998 <clfb> <set c2 = 0> <call -6999>;
~1 <bu 6801>;
~6800 <set c2 = c2 + (`o / 2)> <call -6999>;
+6801 <line -5> "You have $" <apc c2>, <line -1> "`m offers $`o", <line 1> "Do you accept it?" * <bic -6800>;
Following the subroutine we have our somewhat convoluted offer logic, convoluted because we want to have the subject start off with a balance of zero and would also like to have the branch if correct keyword <bic -6800> loop around and detect both the end of our example and not have to have another item following that updating the subject's balance. The interesting and different thing here (beyond using <clfb> to give a bit of a break between offers) is that we're using macros inside a text segment with "`m offers $`o" so when we defined them earlier we didn't put double quotes around them (not to mention the fact that doing so would screw up our balance updater <set c2 = c2 + (`o / 2)>), worse this item file is a Unicode one and may in fact be the first file to ever use Unicode macros in a text segment -- fortunately it appears to work so we're all good, all that hard work I put into that logic pays off, yay! The trick with using macros in a text segment prior to version 6.1.5.2 of DMDX is that Word (in particular) will put RTF control words in between the back tick ` and the macro name if you edit it after having typed it and that would stop DMDX's macro code from seeing your macro definition -- even if it looks good to your eyes however that shouldn't be a problem any more. Should I have overlooked something and you notice a macro being ignored in a text segment you can either re-type it or pour your item file through WordPad which will strip out the Word stupidities.
Dynamic Keyword Parameters Section
As mentioned earlier macros are a convenient way of getting around some of the limitations DMDX has and for a truly dynamic item file they're indispensable. In order to demonstrate this I thought I'd have a display gradually changing colors and utilizing the continue clock on capability with <cco> that allows an item file to update the display without losing subject responses as it's busy assembling and scheduling items and as I was building it I rapidly realized a smoothly changing thing was about five times more complex than I needed and cut it back to this one that has the occasional discontinuity (hopefully infrequently enough to not induce epilepsy in anyone) but it's still about three times more complex than needed…
So what we're going to do is setup a bunch of macros for the RGB values to plug into a background color <bgc> keyword and a writing color <wc> keyword and a bunch of random deltas for those values and then loop around like crazy updating the display till a response is made. All in all 13 macros when 2 would have done but it's more interesting this way so what the hell.
~1 mc+<co>+ <t 20> <mpr +#57> <nfb>
<md .wcR. 255> <md .wcG. 255> <md .wcB. 96>
<md .bgR. 32> <md .bgG. 64> <md .bgB. 128>
<macro set .wcRd. = (random 9) - 4>
<macro set .wcGd. = (random 9) - 4>
<macro set .wcBd. = (random 9) - 4>
<macro set .bgRd. = (random 9) - 4>
<macro set .bgGd. = (random 9) - 4>
<macro set .bgBd. = (random 9) - 4> <xyjustification center>;
+2 <delay 2> <wc `.wcR., `.wcG., `.wcB.> <bgc `.bgR., `.bgG., `.bgB.>
<fm 6> "DMDX" `c <xy .5, .5>
<macro set .wcR. = (`.wcR. + `.wcRd.) & 255>
<macro set .wcG. = (`.wcG. + `.wcGd.) & 255>
<macro set .wcB. = (`.wcB. + `.wcBd.) & 255>
<macro set .bgR. = (`.bgR. + `.bgRd.) & 255>
<macro set .bgG. = (`.bgG. + `.bgGd.) & 255>
<macro set .bgB. = (`.bgB. + `.bgBd.) & 255>
mc+<cco>+ <binr -2>;
Here I've used a mix of legacy single character macro names (c for the clock on) and more readable ones to distinguish writing color RGB values (.wcR. for example) from background ones (.bgR.) and the deltas for those (.wcRd. and .bgRd.).
~1 mc+<co>+ <t 20> <mpr +#57> <nfb>
<md .wcR. 255> <md .wcG. 255> <md .wcB. 96>
<md .bgR. 32> <md .bgG. 64> <md .bgB. 128>
In the first half of our setup item because we're going to use the continue clock on capability <cco> that will catch any keystrokes made between items that might otherwise get missed and because you can't start with <cco> we're using macro c to flip from <co> (the keyword version of the * switch) initially to <cco> for the rest of the executions.
We set a nice low time limit with <t 20> so the colors will change with some alacrity (because we're using <cco> we don't have to worry about giving the subject a reasonable window to respond in as we'll catch the keystroke no matter when it's hit). We've also mapped the space bar as a positive response with <mpr +#57> so you can dismiss the display as if it was an instruction -- which normally would not work so we're going to veer off on a tangent and prattle on about the method of input multisignal command <mip multisignal> for a bit.
Normally in classic DMDX operation you can have multiple buttons mapped to one signal however the converse is not the case (you can't have one button mapped to multiple signals) and only the signal with the highest precedence registers (precedence is request over negative response over positive response) so in order to map the space bar as a positive response one would have to unmap the default request space bar mapping. However with the restructuring of the way input signals are processed some time ago one can now issue the <mip multisignal> command (not shown because it's already active in this introduction) and DMDX will now check multiple signals per button press allowing the space bar to be used for both the request and the positive response without mapping and un-mapping of responses.
~1 mc+<co>+ <t 20> <mpr +#57> <nfb>
<md .wcR. 255> <md .wcG. 255> <md .wcB. 96>
<md .bgR. 32> <md .bgG. 64> <md .bgB. 128>
Getting back to our example you can see we set up three macros for the writing color RGB values and three more for the background that match the colors of the display.
<macro set .wcRd. = (random 9) - 4>
<macro set .wcGd. = (random 9) - 4>
<macro set .wcBd. = (random 9) - 4>
<macro set .bgRd. = (random 9) - 4>
<macro set .bgGd. = (random 9) - 4>
<macro set .bgBd. = (random 9) - 4> <xyjustification center>;
In the second half of our setup item we setup some random deltas for the various RGB values we set up just beforehand with <macro set>. Here a macro instead of being set to a string what's on the right of the equals sign is passed to DMDX's expression evaluator (see the Set Counter help) and then the result is converted to an ASCII string and assigned to that macro. There are some foibles with that expression evaluator, I've looked at it long and hard (I didn't write it, Bob Brodt did in 1985) and one particular part of it is irremediable, other things I've fixed. Fortunately the worst of it is it's functions where the syntax isn't func(value) like almost every other place in the universe, instead it's (func value). I suspect some of it's associativity is also suspect so I tend to bracket almost everything whenever there's the slightest doubt. Here we generate a random number between 0 and 8 and then subtract four from it to give us a -4 to 4 range (typical screens aren't going to see much finer color gradients than a delta of 4 but these sum across iterations so have cumulative effect unless the delta winds up being zero in which case it doesn't change, too bad, it doesn't affect our display unless by the wildest chances we wound up with six of them but I don't think pseudo random number generators are capable of such).
+2 <delay 2> <wc `.wcR., `.wcG., `.wcB.> <bgc `.bgR., `.bgG., `.bgB.>
<fm 6> "DMDX" `c <xy .5, .5>
<macro set .wcR. = (`.wcR. + `.wcRd.) & 255>
<macro set .wcG. = (`.wcG. + `.wcGd.) & 255>
<macro set .wcB. = (`.wcB. + `.wcBd.) & 255>
<macro set .bgR. = (`.bgR. + `.bgRd.) & 255>
<macro set .bgG. = (`.bgG. + `.bgGd.) & 255>
<macro set .bgB. = (`.bgB. + `.bgBd.) & 255>
mc+<cco>+ <binr -2>;
Next we have our colorific item that sets a nice low delay to once again facilitate our animation and then we have our <wc> and <bgc> keywords taking their RGB values from the macros set up earlier. After displaying our target we turn the clock on with macro c that will be a <co> to begin with. Following that we transform our RGB values by the deltas set up earlier so when it gets executed next time around the colors will be slightly different with the <macro set> keyword carefully using a binary "and" to keep the resulting value in the range 0..255 that is a valid RGB value.
Last but far from least we're changing macro c to be the continue clock on variant that will catch keystrokes made as DMDX is between items and then we're telling DMDX to branch if no response is made back to the most recent occurrence of item 2 because this item file uses <bb2mr> (not shown) that tells DMDX when branching backwards to branch to the most recent occurrence of an item, not the first occurrence (which is the default behavior coined when DMTG was written in the late eighties).
More for my own edification than anything else I went ahead and made a version of that that doesn't have the discontinuities when the gun values hit their limits and instead reverses the deltas and it turns out to be a nice demonstration of the power of macros combined with counters because we have to use counters for our gun values and deltas so we can have macros† control a general routine to catch the guns going out of bounds:
~1 mc+<co>+ <t 10> <mpr +#57> <nfb> <macro yikwid>
<set c10 = 255> <set c11 = 255> <set c12 = 96>
<set c13 = 32> <set c14 = 64> <set c15 = 128>
<macro set .wcR. = c10> <macro set .wcG. = c11> <macro set .wcB. = c12>
<macro set .bgR. = c13> <macro set .bgG. = c14> <macro set .bgB. = c15>
<set c20 = (random 9) - 4> <set c21 = (random 9) - 4> <set c22 = (random 9) - 4>
<set c23 = (random 9) - 4> <set c24 = (random 9) - 4> <set c25 = (random 9) - 4>
<xyjustification center> <bu 2>;
~8 <set c`.gv. = c`.gv. + c`.gd.>
<bi 9, (c`.gv. .le. 255) .and. (c`.gv. .ge. 0)>;
~1 <set c`.gd. = c`.gd. * -1> <set c`.gv. = c`.gv. + c`.gd.>;
~9 <return>;
+2 <delay 2> <wc `.wcR., `.wcG., `.wcB.> <bgc `.bgR., `.bgG., `.bgB.> <fm 6> "DM " <xy .5, .5> , <wc `.wcB., `.wcR., `.wcG.> <bgc `.bgR., `.bgG., `.bgB.> <fm 6> " DX" `c <xy .5, .5>;
~3 <md .gv. 10> <md .gd. 20> <call -8>;
~1 <macro set .wcR. = c10>;
~1 <md .gv. 11> <md .gd. 21> <call -8>;
~1 <macro set .wcG. = c11>;
~1 <md .gv. 12> <md .gd. 22> <call -8>;
~1 <macro set .wcB. = c12>;
~1 <md .gv. 13> <md .gd. 23> <call -8>;
~1 <macro set .bgR. = c13>;
~1 <md .gv. 14> <md .gd. 24> <call -8>;
~1 <macro set .bgG. = c14>;
~1 <md .gv. 15> <md .gd. 25> <call -8>;
~1 <macro set .bgB. = c15> mc+<cco>+ <binr -2>;
†Note unless I'm being particularly obtuse I don't think there's an easy way to get a macro to select for another macro although I suspect you can do it by embedding one macro in another using ``
~8 <set c`.gv. = c`.gv. + c`.gd.>
<bi 9, (c`.gv. .le. 255) .and. (c`.gv. .ge. 0)>;
~1 <set c`.gd. = c`.gd. * -1> <set c`.gv. = c`.gv. + c`.gd.>;
~9 <return>;
Here you can see the juicy part of that program where we have a subroutine at item 8 that manipulates counters and which counters are manipulated is based on the contents of two macros, .gv. that selects the counter for the gun value and .gd. that selects for the gun delta value. It's called by the later item pairs starting at item 3:
~3 <md .gv. 10> <md .gd. 20> <call -8>;
~1 <macro set .wcR. = c10>;
Each pair selects a gun value counter and gun delta counter, calls item 8 to iterate the gun value and reverse the delta if it runs out of bounds (and back the gun value back in bounds) and then sets our original gun value macro to the corresponding manipulated gun value counter so it can be passed to <wc> or <bgc> as our original pure macro version did, only this time I tarted it up a bit by making the DM and DX characters different colors:
+2 <delay 2> <wc `.wcR., `.wcG., `.wcB.> <bgc `.bgR., `.bgG., `.bgB.> <fm 6> "DM " <xy .5, .5> , <wc `.wcB., `.wcR., `.wcG.> <bgc `.bgR., `.bgG., `.bgB.> <fm 6> " DX" `c <xy .5, .5>;
Here I've got the gun values for DM mixed up for DX, the problem with wanting to have two colors used in one frame is that the only way you can do that is by using the RTF WYSIWYG features and coloring the individual letters as you please but of course doing that is incompatible with using <wc> so you have to split the different colored sections up into separate frames and merge them all together with commas and use spaces where the different colored letters appear and of course that only works with mono-spaced fonts and here I want the DX in Arial so I've had to fudge it a bit using three spaces, seems to line up nicely on my machines anyway...
Macros really crack open the door of what's possible with DMDX, in addition to what I've outlined here you can do things like treat a range of counters as an array and iterate over it (see the Iterating counters section in the macro documentation, there's also an older example in the Probabilistic Selection Task example in the Dynamic Item content section written before the <macro set> operation was done so it uses a subroutine to set a macro equal to a counter, <macro set> makes it much easier these days). On top that using the push and pop macro operations you can directly manipulate macros as strings (see the anagram task in the macro documentation).
Of course once you're doing something as complicated as either of those examples one rapidly runs into the need to nest calls which is of course verboten unless you specifically allow it by telling DMDX to create a call stack with the <callstack> parameter. I tend to only use just as much stack as I anticipate the item file needing (so if I only have one set of nested calls I'll only use <callstack 2>) so that if I forget a return or what have you DMDX will be able to throw an error clueing me in before too much hair gets lost.
And always remember when all hell breaks loose and it's hit the fan turning the branch diagnostics on with <branchdiagnostics> can really pull it out of the fire...
Test Mode Section
DMDX has several test modes built into it and by exposing them in this built in introduction along with the following Tachistoscopic Acid Tests people can quickly and easily determine whether any particular machine is capable of running DMDX without needing extraneous test scripts.
First up is the Display Test Mode (<testmode 1>) where while waiting for a request DMDX displays the millisecond time, the count of video retraces, the refresh interval based on those two values and then a bunch of error values (more on them below) followed by an indication of whether the video raster is being tracked relentlessly or whether DMDX has geared back to relaxed tracking of it because the video drivers are failing to handle DMDX's relentless tracking. The error tracking values are pretty much only a concern if you see Critical Errors occurring, these days (especially with the Direct3D renderer) you can happily have a constant stream of Timed Out retraces. I would note that a lot of machines will toss a few Critical Errors as the item file starts executing and these can be ignored.
To find out more see the TimeDX Time Video Mode discussion in the help. When you're done watching it hit the space bar.
Test Mode 1
Next up is the Millisecond Callback Latency test (<testmode 2>) where while waiting for a request DMDX displays a test screen with information about the millisecond callback latencies. Currently a particular routine, called millisec() should be called by the OS every millisecond, it is responsible for polling input devices and turning clocks on and dispatching sound event related tasks. With Win32 not being a real time OS this does not in fact occur at exact millisecond intervals, this test mode allows one to see exactly what latencies are involved. Obviously if you care about millisecond accurate RTs you're going to want to run DMDX on a machine where the SD of these latencies is well under a millisecond.
When you're done watching it hit the space bar.
Test Mode 2
While there are a number of other test modes we're going to skip ahead to test mode 13, the Present() time test as the others either require external test hardware or are totally arcane and pretty much only used by me to ascertain how well the code is functioning. When DMDX is using the Direct3D rendering path and is waiting for a request and there is no active display it displays a test screen with information about the time the Direct3D function Present() took to setup the next frame's display. These times should never be more than a few milliseconds and if one is in the order of or longer than the refresh rate there should be a corresponding display error thrown. If used with the original DirectDraw rendering path times will be the time spent calling Flip() till it actually flipped the display.
If you're really keen there are more test mode scripts here:
https://psy1.psych.arizona.edu/~jforster/dmdx/testmodes/
When you're done watching it hit the space bar.
Test Mode 13
Tachistoscopic Acid Test
And of course as those test modes are operating they're not operating in a particularly stressful environment, for that I use a test I refer to as the Tachistoscopic Acid Test and it's proven it's mettle a number of times, notably by detecting that I needed to write a new Direct3D renderer when Windows 8 was released when everything else looked OK. For this test we present 30 numbered frames across the screen for a single tick a piece (so the whole sequence takes a half second on most machines). With a bit of practice one can train one's eyes to see the individual numbers and detect any variation in the rate of their display (indicating display errors as the correct display is totally smooth).
~888 <dfd 1> <dfm 1> mc+<co>+ <t 600> <macro yikwid> <mpr +#57> <nfb>;
+1 `c `m <x .06> "1" / `m <x .0893> "2" / `m <x .1186> "3" / `m <x .1479> "4" / `m <x .1772> "5" / `m <x .2066> "6" / `m <x .2351> "7" / `m <x .2652> "8" / `m <x .2945> "9" / `m <x .3238> "10" / `m <x .3531> "11" / `m <x .3824> "12" / `m <x .4117> "13" / `m <x .4410> "14" / `m <x .4703> "15" / `m <x .4997> "16" / `m <x .5290> "17" / `m <x .5583> "18" / `m <x .5876> "19" / `m <x .6169> "20" / `m <x .6462> "21" / `m <x .6755> "22" / `m <x .7048> "23" / `m <x .7341> "24" / `m <x .7634> "25" / `m <x .7928> "26" / `m <x .8220> "27" / `m <x .8514> "28" / `m <x .8807> "29" / `m <x .91> "30" mc+<cco>+ <binr -1>;
Macro m in our relatively low stress test is defined as nothing with mm++, later on we'll define it as a couple frames in the corners of the screen. When you're done watching it hit the space bar and test mode 1 will run for a bit to see if your machine ran into problems running the acid test. After that it will go through the same sequence but then switch to test mode 13.
Test Mode 1
Test Mode 13
And if you are feeling chuffed that your machine survived all that we can do the same tests but manipulate a much larger section of the screen by merging those numeric frames with a couple of dots at the corners of the screen putting 400 fold or so more work into the equation by defining macro m in the above like this mm+<xy .1,.1> ".",<xy .9,.9>".",+ so each of our simple frames like / `m <x .0893> "2" / instead becomes three frames that merged together manipulate most of the screen every tick / <xy .1,.1> ".",<xy .9,.9>".", <x .0893> "2" /
When you're done watching it hit the space bar and test mode 1 will run for a bit to see if your machine ran into problems running the acid test. After that it will go through the same sequence but then switch to test mode 13.
Test Mode 1
Test Mode 13
Flicker Fusion Test
Back in the bad old days of Windows 98 and XP using the DirectDraw renderer I never would have expected a machine to successfully execute the flicker fusion test but recently when I was fixing the animation <AgainWhen> keyword so it would work with the Direct3D renderer it occurred to me that I could build a test like TimeDX's Refresh Rate test that displays two sets of angle brackets against themselves (so <> and ><) to facilitate detecting dropped or delayed frames. Turns out Windows 10 machines using the Direct3D renderer are largely capable of doing so these days, the exception being one of the machines here where it flickered every now and then (it's a laptop with a large TV plugged into it so that might have something to do with it). Even though DMDX couldn't see any display errors something was clearly busted deep in the 3D rendering pipeline, I even built a couple of new test modes to double check DMDX's error checking. This unfortunately is the Achilles heel of using Direct3D for a renderer, it's so buffered that unless things really go out to lunch DMDX doesn't necessarily get feedback that things are going sideways. Beyond getting yourself one of those high frequency video cameras and videoing the screen to see just what's up there's not much we can do about it as DirectDraw is not an option anymore because it's emulated in Windows 8 and later OSes and drops frames left and right even when lightly loaded (let alone the likes of these tests). The most you can do is present a long chain of your largest frames at one tick intervals and check the test mode 13 Present() times (which the last iteration of the tachistoscopic acid test does), the idea being to load up the 3D rendering pipeline to the point that it has to start blocking DMDX's requests and only then can we get an idea of how long that machine takes to actually render your targets. I suspect all 3D chipsets these days are capable of sustained presentation of large displays -- unless something has gone wrong of course as the afore mentioned machine clearly demonstrates.
So for a final burn it down test we'll use the <AgainWhen> keyword where you feed it the the number of times a frame should be redisplayed and how many ticks in the future it should be done at -- and yes, it is abortable with <aie> and <AbortDQpurge> so if you wanted to use it to actually test subjects with and not have them have to wait around till the sequence finishes you could, something I'm not doing here because I don't want to be throwing away CPU cycles unnecessarily in a testing environment, instead I'll turn <safemode 1> on so you can abort the longer displays if you need to. And we'll use it to run 8 second uninterrupted sections (ie your response won't stop them) of frames of alternating brackets that should appear as a nice set of four small Xs with no flickering:
~1 <testmode 1> <safemode 1> <dfm 1> mc+<co>+ <t 8000> <macro yikwid> <mpr +#57> <nfb>;
+2 `c / <fm 2> <aw 240, 2> "Refresh Rate ><" %1 / <fm 2> <aw 240, 2> "Refresh Rate <>" %1 / mc+<cco>+ <binr -2>;
Mind you if your machine fails at this don't be too concerned as long as DMDX's diagnostics are good -- unless you want flicker fields displayed of course. I suspect seeing the occasional flicker here won't affect normal use of DMDX, it's likely only under the extreme stress of displaying a frame every tick that inconsistencies show up, absent that and DMDX is tracking the raster reliably you're probably good to go.
Test Mode 1
Test Mode 13
Postscript
DMDX can use AMD FreeSync and NVIDIA G-SYNC displays with the freesync modifier to the <videomode> keyword allowing arbitrary millisecond presentation intervals down to the native refresh interval (usually 7 ms or so). It can also drive NVIDIA's 3D Vision stereoscopic goggles with the nvidia3D modifier.
In addition to the scrambling routines outlined earlier if you've really got a death-wish and your scrambling needs aren't met by those relatively simple routines (say you have two or more sets of items that need scrambling) the Multi-Scramble routines have almost limitless capability and there are more examples in the the help. If that doesn't get it DMDX can also present items repeatedly or in any sort of order using branching with counters and/or macros as detailed in the Dynamic Item content notes.
DMDX can run infant attention studies with the <lookingtime> keyword, it can drive fMRI based experiments with <recordclockontime> and <id pio12> with <output> and as of version 6.1.0.0 DMDX can send TCP/IP packets to other machines or other applications on the local machine to control devices such as the Gazepoint eye tracker using their Open Gaze API with <id tcpip> and <send>. Generally there's almost no limit to what it can do, given that it's Turing Complete, worst case you get to learn a bit of low level programming.
DMDX has it's own data analysis package, ANALYZE, that can perform ANOVAs or produce .CSV files for mixed effect analysis. Other utilities like UnloadAZK for gathering data files collected on multiple machines or AZK2CSV that facilitate porting data to programs that read .CSV files like Excel are also available along with ANALYZE here:
https://psy1.psych.arizona.edu/~jforster/dmdx/dmdxutils.zip
DMDX itself is available here:
https://psy1.psych.arizona.edu/~jforster/dmdx/DMDX.ZIP
Please note that we distribute this software as is, with no guarantees as to its reliability. It may not be redistributed for sale.
The DMASTR software is distributed free of charge, and we ask only that you acknowledge use of the system in publications (e.g., "The experiment was run using the DMASTR software (DMDX) developed at Monash University and at the University of Arizona by K.I.Forster and J.C.Forster."). You can also cite our DMDX paper: DMDX: A Windows display program with millisecond accuracy. Forster, K.I. & Forster, J.C. Behavior Research Methods, Instruments, & Computers (2003) 35: 116. https://doi.org/10.3758/BF03195503
Although the software has been in constant use for more than 20 years, it is constantly evolving, and hence there is always the possibility that new releases may contain bugs. We do not assume any legal responsibility for the software, but we do assume the responsibility of advising you as rapidly as possible of any bugs that we do detect. For this reason, it is important that you get your name onto the user list serve if you plan to use the system on a regular basis. To do so:
1: Send a message to list@list. arizona. edu from the address you want to subscribe to the list.
2: In the subject line of your message, type in: subscribe dmdx Firstname Name (replace 'Firstname Name' by your own first name and name).
Once your subscription is confirmed posts can be made to dmdx@list.arizona.edu. Please don't send attachments to the list. Your subscription should also be manageable by pointing your browser at:
https://list.arizona.edu/sympa/info/dmdx
That's the end (hit ESC to exit)
If you save the data it will probably wind up in the temporary directory with the name introduction.azk and because the introduction is chained one full run of data will be spread over three or more subjects in introduction.azk, the stroop section will be in introduction2.azk, the <zil> <zor> rating section will be in introduction3.zil, the <zil> <ztr> subtract 7 stressor will be in introduction4.zil and the macro section will be in introduction5.azk. And yes, chaining stuff complicates data saving, if we were actually gathering real data with such a file use of the subject ID becomes almost mandatory.
And if you really need to see what this item file looks like (the text is available in the help's How To Use It section) use a resource editor and pull resources 112, 113, 114, 115 and 116 from DMDX.EXE -- none of which I recommend, this file is exceeding hard to parse in places with all it's SimSun full width quotes.