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!
By the way, could you please explain what you mean when you say “Input Controls”? Key has not been defined?
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?
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”
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
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
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; XLISP caseCongratulations, with just a little tweaking it works
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 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?
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 been an Audacity user for quite a while now, and I’ve used it for a multitude of projects. Lovely program it is.
Of late, I’ve come into a project when I need to use the old Bell System MF tones. I have the barest idea of how to use the Nyquist language, so I have no idea how to write the plugin myself. I had the idea of modifying the DTMF generator, but I have no idea how to go about doing it (also, there’s the fact that the DTMF system doesn’t generate in the same manner as the Bell (R1) MF system).
For anyone who is interested in helping out by writing this generator, here’s the pertinent information regarding the Bell MF signalling system (which is the same as the ITU “R1” signalling scheme).The tones used by the system are:
- KP 1100 Hz + 1700 Hz
- ST 1500 Hz + 1700 Hz
- 1 700 Hz + 900 Hz
- 2 700 Hz + 1100 Hz
- 3 900 Hz + 1100 Hz
- 4 700 Hz + 1300 Hz
- 5 900 Hz + 1300 Hz
- 6 1100 Hz + 1300 Hz
- 7 700 Hz + 1500 Hz
- 8 900 Hz + 1500 Hz
- 9 1100 Hz + 1500 Hz
- 0 1300 Hz + 1500 Hz
(If you’ll notice, the digit tones of the MF system follow a two of five code, with the 700, 900, 1100, 1300 and 1500 values corresponding to 0, 1, 2, 4, 7; the KP (start of pulsing) tone and ST (end of pulsing) tone use the 1700 tone plus 2 and 7, respectively. The European R2 MF signalling system uses the other combinations of 1700 with 0, 1, and 4 for more tones, but nevermind the R2 system.)
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. Transmissions start with KP, and end with ST, tones are sent “en bloc”, so no pauses.
I.e. a number dialed as “1 (pause) 800 (pause) 555 (pause) 1212”, would be sent as “KP 18005551212 ST” (where the spaces are only inserted for clarity).
In terms of input to the generator, all that is really needed is the input of the number to be generated (with KP and ST generated automatically), and the volume.
Thanks to any one who is willing to take on this task, I wish I could be of more help.
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.
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))
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.
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?
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.
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: XLISP if
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.
This is my first time writing anything in LISP, so I hope it doesn’t contain too much strange syntax.
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.