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

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:

  1. KP 1100 Hz + 1700 Hz
  2. ST 1500 Hz + 1700 Hz
  3. 1 700 Hz + 900 Hz
  4. 2 700 Hz + 1100 Hz
  5. 3 900 Hz + 1100 Hz
  6. 4 700 Hz + 1300 Hz
  7. 5 900 Hz + 1300 Hz
  8. 6 1100 Hz + 1300 Hz
  9. 7 700 Hz + 1500 Hz
  10. 8 900 Hz + 1500 Hz
  11. 9 1100 Hz + 1500 Hz
  12. 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.

There’s “helping” and there’s “writing”.
As I’m not employed to do this, I only generally write plug-ins that are of particular interest to me, but I’d be more than happy to help you write a plug-in :wink:

As a start, try running the following snippets in the “Nyquist Prompt” effect (in the Effect menu). To run these snippets of code, select a short part of an audio track, then open the Nyquist Prompt effect, copy and paste the code into the Nyquist Prompt text box and click the OK button.

; this is a comment
; comments start with a semicolon
; KP 1100 Hz + 1700 Hz

(setf kp
  (mult 0.4
    (sim (hzosc 1100)
      (hzosc 1700))))



; ST 1500 Hz + 1700 Hz

(setf st
  (mult 0.4
    (sim (hzosc 1500)
      (hzosc 1700))))



; 1 = 700 Hz + 900 Hz

(setf st
  (mult 0.4
    (sim (hzosc 700)
      (hzosc 900))))

Can you create the others?
Next will be sorting out the timing.

Well, that’s to be expected really, though I do have to say me and functional languages don’t go well together. (My experience with trying to be forced to learn one went badly, and that’s soured my view of such languages, even though I do see the power behind them.)

But enough about my issues with functional languages.

Which works quite well I must say. Save for the whole “applying to some stretch of previously made track of variable length”.


The others were easily able to be created, I mean it’s changing a few comments and constants; now, if I were writing this in C:

switch(digit) {
    case 0:
        intHiTone = 1500;
        intLoTone = 1300;
        break;
    case 1:
        intHiTone = 900;
        intLoTone = 700;
        break;
    case 2:
        intHiTone = 1100;
        intLoTone = 700;
        break;
    case 3:
        intHiTone = 1100;
        intLoTone = 900;
        break;
    case 4:
        intHiTone = 1300;
        intLoTone = 700;
        break;
    case 5:
        intHiTone = 1300;
        intLoTone = 900;
        break;
    case 6:
        intHiTone = 1300;
        intLoTone = 1100;
        break;
    case 7:
        intHiTone = 1500;
        intLoTone = 700;
        break;
    case 8:
        intHiTone = 1500;
        intLoTone = 900;
        break;
    case 9:
        intHiTone = 1500;
        intLoTone = 1100;
        break;
    case 10:
        intHiTone = 1700;
        intLoTone = 1100;
        break;
    case 11:
        intHiTone = 1700;
        intLoTone = 1500;
        break;
}

To select the frequency pairs, and with that I’d then replace the constant values of the generating code with the intHiTone and intLoTone variables. Gives me the right digit pairs.

But I have no idea how to do the equivalent of a C “switch()” expression in the Nyquist language, so I have no idea how to go about doing that. I also don’t know how to create specific lengths of silence which is what I would thing is the easiest way to get the tones produced “perfectly”.


Thank you for the assistance with this.

OK, point taken - I’ll be gentle :smiley:

Ah yes, the stretching. This is one of the trickiest parts to get your head round because it involves “local time”, “global time” and “warp”, but fortunately we don’t need to go deeply into this and can get by with a couple of “rules”:

  1. For “effects”, time is treated as “local time”. This means that the current selection counts as “1 unit of time”.
  2. For “generate” type plug-ins, time is treated as “global” (“real”) time, so “1 unit” of time is 1 second.

What this means in practice:
If we run (osc 60 2.5) in the Nyquist Prompt (which is an “effect”), the second parameter (2.5) is the duration and the command will generate a tone that is 2.5 “units” duration - that is, 2.5 times the duration of the selection.
If we run the same code in a generate type plug-in, it will generate 2.5 seconds of the sine tone.

The Nyquist Prompt provides a convenient way to test short scripts, but we need a way to make it use “real” time rather than “local” time. There is a function provided for this called abs-env. What this function does is to “evaluate its arguments in the default environment”. In other words, wrapping a function in abs-env will force that function to use “real” time.

As an example we can use abs-env with hzosc (which we used previously).

; this will produce a 440 Hz tone that is the same length as the selection
(hzosc 440)



; this will produce a 440 Hz tone that is 1 second long
(abs-env (hzosc 440))



We can do a similar thing in Nyquist (and XLISP, on which Nyquist is based).
http://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-047.htm

(setq digit 1)

(case digit
  (0 (print "digit 0"))
  (1 (print "digit 1"))
  (2 (print "digit 2"))
  (t (print "any other")))

As the MF tones all require exactly two frequencies, we can create a function with one argument for each frequency. We also want to set the duration so we’ll use an additional argument for the duration.

The hzosc function that we have been using so far does not take a parameter for duration. Rather than messing about with stretching, we will use the function osc to generate the tones as this does take duration as a parameter http://www.cs.cmu.edu/~rbd/doc/nyquist/part8.html#index361

;;; generate the MF tone
(defun mftone (hz1 hz2 dur)
  (mult 0.4
    (sim (osc (hz-to-step hz1) dur))
      (osc (hz-to-step hz2) dur)))

; call the function
(mftone 1100 1700 0.1)

As expected, this does not work quite right in the Nyquist Prompt because it is an “effect” and is using “local” time, so the duration ends up as 0.1 times the selection.
To fix this we need to evaluate out function in the default environment (“real time” not “local”).

;;; generate the MF tone
(defun mftone (hz1 hz2 dur)
  (mult 0.4
    (sim (osc (hz-to-step hz1) dur))
      (osc (hz-to-step hz2) dur)))

; call the function
(abs-env (mftone 1100 1700 0.1))

One last bit for now as it’s late now and I need sleep.

;;; generate the MF tone
(defun mftone (hz1 hz2 dur)
  (mult 0.4
    (sim (osc (hz-to-step hz1) dur))
      (osc (hz-to-step hz2) dur)))

;; call the function
(abs-env 
  (sim
    (at 0 (mftone 1100 1700 0.1))
    (at 1 (mftone 700 1700 0.1))
    (at 2 (mftone 1100 500 0.1))))

Perhaps I won’t need these tones myself. Nevertheless it’s an interesting task, so I gave it a try too.
I’ve been playing with some recursive code (loops get boring over time).
I only wanted to contribute some code for the sorting out of the text input but ended up with nearly the whole plug-in.
There is in principle only the plug-in header missing.
However, there are some things I am not certain about.
Are there any other inputs besides the text box for the number desired (e.g the volume)?
Have the durations to be varied, as described above, or are the precise values satisfactory?
I fancy, it would be nice when the durations would be varied such that the sine tones end with a full cycle, in order to avoid clicks. Please test the snippet:

;; A test input:
(setf input "01 234 567 89")

;; the recursive function
(defun number-to-tone (num-stream &aux low-high dur)
  (setf current (pop num-stream))
  (unless current (return-from number-to-tone s))
  (setf dur (if (equal current #>) 0.1 0.068))
  (setf low-high
        (case current
              (#> '(1100 1700)); KP
              (#< '(1500 1700)); ST
              (#1 '(700 900))
              (#2 '(700 1100))
              (#3 '(900 1100))
              (#4 '(700 1300))
              (#5 '(900 1300))
              (#6 '(1100 1300))
              (#7 '(700 1500))
              (#8 '(900 1500))
              (#9 '(1100 1500))
              (#\0 '(1300 1500))
              (t nil)))
  (seq (number-to-tone num-stream) 
       (if low-high
           (sim (osc (hz-to-step (first low-high)) dur)
                (osc (hz-to-step (second low-high)) dur)
                (s-rest 0.136))
           (s-rest 0))))

;;; Start of the main program
(setf *sr* *sound-srate*)
; initialize s to 0 length
(setf s (s-rest 0))

; String to chars
(setf numbers
      (cons #<  ; append st 
            (reverse (cons #>  ; append kp 
                     (get-output-stream-list 
                        (make-string-input-stream input))))))

; Return sound
(abs-env (mult 0.4 (number-to-tone numbers)))

I was thinking that this might be a good use for an a-list:

;; association list of parameters
(setf params 
  '((KP . (1100 1700))
    (ST . (1500 1700))
    (1 . (700 900))
    (2 . (700 1100))
    (3 . (900 1100))
    (4 . (700 1300))
    (5 . (900 1300))
    (6 . (1100 1300))
    (7 . (700 1500))
    (8 . (900 1500))
    (9 . (1100 1500))
    (0 . (1300 1500))))

(setq mylist '(KP 1 2 3 4 5 4 3 2 1 ST))

(dotimes (i (length mylist))
  (print (assoc (pop mylist) params)))

I’d certainly be interested in creating a plugin of this sort. Would I be able to create it in a text editor like Notepad, and then save it as a .ny file?

Hi Annabelle

You could paste my code directly into the Nyquist prompt and test it as if it were a plug-in.

Just add an appropriate header (copy one from a existing Generate plug-in, for instance)

You’ll also need a text control for the input variable.

The code can now be saved directly from within this prompt.
This works with any kind of snippet and you can save with any extension you like, *.ny is only needed for plug-ins.

However, I still favour Notepad++ to create plug-ins.

Cheers
Robert

So for example, the headers and code would probably be something like the one in this file?
dtmf.ny (3.09 KB)

I Would probably take a newer example such as:

;nyquist plug-in
;version 4
;type generate
;categories "http://lv2plug.in/ns/lv2core#GeneratorPlugin"
;name "Click Track..."
;action "Generating Click Track..."
;info "For help, select one of two help screens in 'Action choice' below."
;author "Dominic Mazzoni"
;copyright "Released under terms of the GNU General Public License version 2"

From this, you can create your own template - a text file on the desktop for instance - and copy it into each new plug-in.

A few remarks:
The first two lines should never change.
Version 4 plug-ins have the most features, thus, I would stick to it.
The type might change, there are also process and analyze available.
;categories is not really necessary - it’s a bit confusing to find the right type online.
;name and ;action have to be adapted for each new plug-in.
Action is what will be displayed on the progress bar when executing an effect.

;info seems to be no longer in use, I cannot see any text displayed in the Dialog.

;author and ;copyright appear under the manage menu (alt+m) of the effect dialog, under “about” to be more specific.
Confusingly enough, they are renamed:
;author → Vendor
;copyright → Description

Robert

As Robert says, better to use a newer example.

Each line of the header begins with a single semicolon followed by a keyword and then one or more parameters. The following four headers are required (essential) for all plug-ins. The keyword must always be typed in lower case. The parameters must also be lower case unless the parameter is quoted text (such as the name of the effect).

;nyquist plug-in
This tells Audacity that it is a Nyquist plug-n

;version 4
Plug-ins may be version 1 to 4. Version 4 is the most recent and as Robert says, has the most features. New plug-ins should use the most recent version number.

;type process
or
;type generate
or
;type analyze
The type tells Audacity which menu to put the plug-in into. Process type plug-ins go in the Effect menu, Generate type plug-ins go into the Generate menu and analyze type plug-ins go into the Analyze menu. As a general rule, plug-ins that process audio and return audio to the track should be ;type process, plug-ins that generate audio should be ;type generate, and plug-ins that analyze audio or create labels should be ;type generate.

;name “Quoted name of track”
This is the name that appears in the Audacity menu. Try to use a short but descriptive name. If the plug-in has an interface the name should end with three dots. If the plug-in acts immediately without showing an interface the name should not have the three dots. The name must be in quotes and is usually in Titles Case.

Those are the 4 essential headers.

The ;categories and ;info headers are not currently used by Audacity (ignored).

Plug-in headers are documented here: http://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference#Nyquist_Plug-in_Header

I think this might be what the plugin should look like. Look carefully at this and tell me if it looks right to you. Tell me if there’s anything in the code that I may need to change.
R1MF Tones.ny (1.07 KB)

The headers look OK except that there are 4 spaces at the start of each line. The header lines should really start at the beginning of the line.
Also, there would usually be an empty line separating the header lines from main plug-in code.

However, it will not work as a plug-in because the code is incomplete. If you try to run it using the Debug button you will get an error beginning with:

error: unknown character name - “KP”

How’s this?
R1MF Tones.ny (818 Bytes)

That’s got rid of the spaces at the start of the lines.

Normally when writing LISP code (and Nyquist is a type of LISP code), some lines are indented by spaces so that the code is more readable (for sighted users). I realise that can be difficult, and of no benefit, if you can’t see the code, so don’t worry about indenting the code. If necessary I can add appropriate indentation for any plug-ins that we wish to upload to the Audacity wiki.

The code is still incomplete, so it still does not run. Specifically, in the first line of Nyquist code you have:

(setf low (if (member key

but “key” has not been defined. I’m guessing that “key” would be an input variable, but currently there are no input controls.

By the way, I notice that there are error in the code that Robert posted earlier in this post: https://forum.audacityteam.org/t/req-r1-mf-blue-box-tone-generator/28450/7
To specify a character, the correct syntax is a hash symbol followed by a backslash followed by the character. For example, for the character “A” :
#\A

In several places the backslash is missing.

Here is Robert’s code with corrections:

;; A test input:
(setf input "01 234 567 89")

;; the recursive function
(defun number-to-tone (num-stream &aux low-high dur)
  (setf current (pop num-stream))
  (unless current (return-from number-to-tone s))
  (setf dur (if (equal current #\>) 0.1 0.068))
  (setf low-high
        (case current
              (#\> '(1100 1700)); KP
              (#\< '(1500 1700)); ST
              (#\1 '(700 900))
              (#\2 '(700 1100))
              (#\3 '(900 1100))
              (#\4 '(700 1300))
              (#\5 '(900 1300))
              (#\6 '(1100 1300))
              (#\7 '(700 1500))
              (#\8 '(900 1500))
              (#\9 '(1100 1500))
              (#\0 '(1300 1500))
              (t nil)))
  (seq (number-to-tone num-stream)
       (if low-high
           (sim (osc (hz-to-step (first low-high)) dur)
                (osc (hz-to-step (second low-high)) dur)
                (s-rest 0.136))
           (s-rest 0))))

;;; Start of the main program
(setf *sr* *sound-srate*)
; initialize s to 0 length
(setf s (s-rest 0))

; String to chars
(setf numbers
      (cons #\<  ; append st
            (reverse (cons #\>  ; append kp
                     (get-output-stream-list
                        (make-string-input-stream input))))))

; Return sound
(abs-env (mult 0.4 (number-to-tone numbers)))

and for ease of downloading, here it is as a text file:
robert-r1mf-code.txt (1.33 KB)