REQ: R1 MF ("Blue box") Tone Generator

I actually tried to format it sort of the same way as the DTMF Tones Plugin, but of course I changed the frequency parameters to match those of the R1MF Tones. By the way, could you please explain what you mean when you say “Input Controls”? Key has not been defined? I’m confused!

At the top of your code it says:

(if (member key '(#\1 #\2 #\4 #\7)) …

The function “member” looks for an expression in a list (see: XLISP member)
So your “IF” statement is saying, “if the value of key is in the list of characters 1, 2, 4, 7, then do something, else do something else”. The problem is that the value of “key” has not be defined.

Who wrote that code?

Compare the code I made to the code I loosely based it on.
dtmf.ny (3.09 KB)

In David’s code he has defined a function with 5 parameters. See the line:

(defun dtmf (key volume tl twist sl)

That is the start of the “function definition”. He has named the function “dtmf” and the 5 “arguments” are “key”, volume", “tl”, “twist” and “sl”.
This function is called in the final line:

(dtmf (char keys i) volume tl twist sl)

See that the “function call” passes 5 values to the function. These are “(char keys i)”, “volume”, “tl”, “twist” and “sl”. So the first parameter in the function, which is called “key” is given the value of (char keys i).

Within SEQREP (which is a loop structure), the value of “i” acts as a counter, so each time it loops, (char keys i) takes the next character from “keys”, where “keys” is a “string” (a text value) that is set by the first control:

;control keys “Tone string” string “” “180audacity”

Try this out, and see if there’s something I’m forgetting.
R1MF Tones.ny (1.42 KB)

You can’t use #\KP or #\ST because the "#" is for specifying a single character. You could use #\K and #\S instead.

There were a number of mismatched parentheses (the closing brackets not matching up correctly with the open brackets). Perhaps Robert could give some tips for checking that the parentheses are correctly matched.

You have a repeat of (r1mf (char keys i) volume tl twist sl) at the end of the file that should not be present.

You missed out a few “IF” statements.

In the part where you set the volume to zero if the character is not one of the full list of characters, you have #\4 twice.

The “Twist” control is not increasing the volume of the high tone, it is decreasing the volume of the low tone.

Attached is your file with the above corrections. I also put some spaces in and indented some lines so that I could read the code more easily.
R1MF Tones-4.ny (1.72 KB)
Note that in this version, when you test to see if the character is a “K” or and “S”, the characters are case sensitive. It might be worth also checking against lower case “k” and lower case “s”.

Stylistically it would be better to use the “CASE” statements rather than nesting lots of “IF” statements (and it would make the code easier to read and less prone to mismatched parentheses).
See; http://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-047.htm

Congratulations, with just a little tweaking it works :slight_smile:

As the originator of this post puts it, “The KP tone must being 100 ms long, plus or minus 10 ms; and the digit and ST tones must be 68 ms long, plus or minus 7 ms. There must be 68 ms of silence
between each signal, plus or minus 7 ms.”. How would I configure that, I wonder? Could you tell me which parameters to fix so I can make the plugin completed all the way?
R1MF Tones.ny (2.06 KB)

As “KP” is always at the beginning and “ST” at the end, I’d suggest that you make them “options” rather than requiring them to be entered in the text input. So for controls:

;control keys "Tone string" string "" "7149642437"
;control kp-st "KP and ST tones" choice "KP and ST,KP only,ST only,Neither" 0
;control tl "Tone duration [milliseconds]" int "" 100 1 1000
;control sl "Silence duration after tone [milliseconds]" int "" 100 0 1000
;control twist "Twist [decrease volume of low tone in each tone; dB]" real "" 0.0 0.0 4.0
;control volume "Volume [percent]" int "" 80 1 100

You would then remove the “K” and “S” characters from the character lists (so the lists are now numeric characters only).
Then replace the final line of the code with something like:

(let ((numeric (seqrep (i (length keys)) (r1mf (char keys i) volume tl twist sl)))
      (kp (mult volume (sim (osc (hz-to-step  1100) 0.1)
                            (osc (hz-to-step  1700) 0.1))))
      (st (mult volume (sim (osc (hz-to-step  1500) 0.068)
                            (osc (hz-to-step  1700) 0.068)))))
  (case kp-st
    (0 (seq kp (s-rest sl) (cue numeric) (cue st)))
    (1 (seq kp (s-rest sl) (cue numeric)))
    (2 (seq numeric (cue st) (s-rest sl)))
    (t numeric)))

I’ve compiled a plugin for R1MF Tones, and it works. Here it is.
R1MF Tones.ny (2.62 KB)

There’s still a few “#\K” and “#\S” characters that should be deleted, for example, line 28.

Is this thaepart you’re talking about?

(if (member key '(#\K #\6 #\9))
1100

(if (member key '(#\S)) 1500 0))))))
(if (member key '(#\K #\S))
1700
(if (member key '(#\K #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\0 #\S))

Yes.
The first line is looking to see if the input character “key” is in the list '(#\K #\6 #\9), but we don’t need to look for the character #\K because we are now dealing with “KP” and “ST” separately.

Is this one better?
R1MF Tones.ny (2.54 KB)

That’s better, but you’ve missed a couple. The straightforward one is in line 51. The other is a bit more interesting, see lines 47 to 50. Do you understand what those lines do? What do you suggest doing there?

To be totally honest with you, I’m confused on what those lines do. What would you suggest I do to fix them?

Do you understand how “IF” works?

The basic idea is that the code will do something “IF” a specified thing is true, and (optionally) can do something different if it’s not true. This type of structure is called a “conditional” because it does something subject to a specified condition being met. A simple example would be:

(if (= (+ 1 2) 3)
    (print "1 plus 2 equals 3")
    (print "1 plus 2 does not equal 3"))

Of course this silly example will always print “1 plus 2 equals 3”.
There are more examples in the XLISP manual: http://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-137.htm
It’s worth reading that part of the manual before going further.

In your code there are a whole load of “IF” statements that are “nested” one inside another. The code says, "If this is true, then do something, else, if something else is true then do another thing, else if something else is true do another thing, else … and so on. This is a rather inelegant way of writing the tests (there are better ways), but it works so let’s stick with it for now.

The next thing to understand is what the “condition” is that we are testing for true or false.
Let’s take one particular example:

(if (member key '(#\1 #\2 #\4 #\7))

Here we have an “IF” statement that looks to see if “(member key '(#\1 #\2 #\4 #\7)” is true or false.
In plain English, it is looking to see if the value of “key” is in the list of character “1, 2, 4, 7”. The “hash backslash” means “a character”.
So if, for example, the value of “key” is character “2”, then the value of “key” is a member of the list so the condition is “true”, but if, for example, the value of “key” is character “3”, then it is not a member of the list and the condition is “false”.

Now if we look at this block of code:

  (setf high
    (if (member key '(#\1))
        900
        (if (member key '(#\2 #\3))
            1100
            (if (member key '(#\4 #\5 #\6))
                1300
                (if (member key '(#\7 #\8 #\9 #\0))
                    1500
                    (if (member key '(#\K #\S))
                        1700
                        0))))))

We are setting the value of “high” conditionally.
If the value of “key” is in the first list (which contains only the character “1”), then high is set to 900, else (if it is not in that list), we look to see if the value of “key” is in the next list (containing the number 2 and 3). If it is in the second list then we set the value of “high” to 1100, else, if it is not in that list we ask if the value of “key” is in the list '(#\4 #\5 #\6), and so on until we get to the end of this block of code.

The final “IF” statement looks to see if the value of “key” is in the list containing the characters K and S. If it is, then we set “high” to 1700, otherwise we set “high” to zero.
We don’t actually want this final test because we are no longer accepting the characters K or P as valid input, so, going back to the previous “IF” statement, the code asks if the value of “key” s in the list containing the characters 7, 8, 9 and 0. If it is in that list, set “high” to 1700, otherwise (and here we don’t want to test the value of “key” any more because we have tested against all valid characters), we just want to set “high” to zero.

Does that make sense? Can you work out how to do that? Have a go and be careful to get the open and close parentheses to match up correctly.

Now my mind is in a whirlpool! Interesting how I got all those “if” statements from the “DTMF” plugin, and loosely based my R1MF plugin on that one. Changing the number characters to fit the R1MF Tones, of course.

I created a fairly complete MF plugin. It can do 0-9, KP, KP2, ST, ST2 and ST3 tones. It represents KP2 as KP’ and ST2, ST3 as ST’, ST’’ respectively. You can get it from my github repo. It was inspired by the techniques suggested in this thread, as well as some of my own research. This is my first time writing anything in LISP, so I hope it doesn’t contain too much strange syntax.

I plan to do a little more work on it, mostly to make sure it handles invalid input in a sane way and to document it a little bit better.

Congratulations mharriger. I don’t know much about MF, but the LISP / Nyquist code seems to work all right.

The syntax looks fine, though the formatting (indentation) looks a bit strange in places (not very LISP-like).
There’s a good, short guide to formatting LISP code here: Indenting Common Lisp
Most experienced LISP programmers tend to follow that guide pretty closely, regardless of which dialect of LISP.

As additional guides to good indentation, there is the XLISP manual (XLISP: An Object-oriented Lisp) and also the Nyquist .lsp files (https://github.com/audacity/audacity/tree/master/nyquist)

Some of the old Nyquist plug-ins are formatted quite badly, so not a good guide.

I also came across this short “style guide” recently. It does not all apply to Nyquist, but there’s some good general tips in it (like writing short functions that do one thing): http://people.ace.ed.ac.uk/staff/medward2/class/moz/cm/doc/contrib/lispstyle.html

Congratulations mharriger. I don’t know much about MF, but the LISP / Nyquist code seems to work all right.

Thanks! And thank you for the guide to LISP formatting, I’ll take a look at it and see about reformatting the code. It was a struggle to keep the parens matched up, good indentation seems like it would help.