Convert scientific data to tones

Hello - I am totally new. I’m a scientist who collects at a lot of data. For some time, I’ve thought it would be interesting to convert some of these data (time series, look like wiggly curves) into musical tones to “play” the data.

A tip online suggested to try Audacity, since it can import raw text files. However, there must be lots more to it than that. As an example, I’ve tried to convert one data set, consisting of 107 observations, into tones by calculating the mean of the data (4.85), setting that equal to 440 Hz (the tone A, yes?), and then converting the rest of the data to ratios of that (see attached image, a graph of the data).
Data normalized to the mean as A.png
No matter what settings I choose for importing the raw data file, what shows up is a data set that looks pretty much like the next image (filename = window000), and when I play it, it sounds buzzy.
window000.png

Ideally, for each of the 107 data observations, I would like to play a note that has a duration of about 1/8 to 1/4 second, so I can hear it, and that the value of 1 would be equivalent to the musical note A (for this example).

Any help is much appreciated, thanks!

“Tones” are by definition “periodic” waveforms. Simply repeating a series of data will fulfil that definition, but 99.9% of the time it will sound “buzzy” because of the abrupt jump from the end of one cycle to the start of the next. That “jump” will create a complex series of harmonics right up to the Nyquist frequency (half the sample rate) and will dominate the timbre of the sound.

I also note that in your example data set (graph), the y values are all positive (approx. +0.75 to +2.25). To create a waveform that is correctly centred on the audio track you would need to find the average value of the data, then offset each sample value by that amount. Your modified data set will then be signed values, which you can normalize to fit in the range +/- 1.0 (0 dB).

Thank you, steve. I will re-normalize the data to fall between +/- 1.

However, how does one convert a single data point into a single musical note? That may be the crux of my problem.

Audacity is designed to be an audio recorded and editor, so that feature is not directly built in, but yes there are ways of doing it.
As you deduced, “Import Raw” is one method, but the “trick” is to ensure that the format of your data and the format of the import are the same.

“Import Raw” is expecting to see a series of binary values. It supports a number of binary formats, such as “16 bit signed integer”. For example, if you have a file that contains one “signed 16 bit integer value” and import as “signed 16 bit integer”, then that value will appear in the audio track as one data point (a “sample”).

What format is your data?

My data are numerical. I re-normalized them to fall between +1 and -1. The first 10 points look like this:

0.090
0.112
0.153
0.223
0.288
0.358
0.417
0.472
0.505
0.535

This is saved as a “plain vanilla” MS-DOS .txt file, using the Windows accessory called Notepad. When looking at the Properties of the file, it’s not apparent what sort of bit file it is stored as, e.g., 8-bit signed or unsigned, etc. Is there a way to determine that?

Thank you again.

Your data is numerical text.

Text characters are not the same as pure (data) numerical values. For example, in ASCII encoding, the numerical value of the text character “3” is (decimal) 51 ASCII - Wikipedia

What is required is that your text values are converted into raw numerical data. That is, the text characters “0.123” are converted to a “value” of 0.123

There are several ways that can be done. Normally it would be done through programming. If you have any programming experience (almost any language) it should be fairly straightforward to convert from plain ASCII text to numerical values and output a file containing the raw binary data. That output file could then be imported into Audacity using “Import Raw”.

If you don’t have previous programming experience, then fortunately Audacity includes a relatively simple scripting language called “Nyquist” that allows users to write custom code for use in Audacity. The simplest way to enter Nyquist commands is via the “Nyquist Prompt” Audacity Manual
More complex Nyquist code can be written as “plugins”. For example, the “Sample Data Export” tool (Audacity Manual) is written in Nyquist. If you want to spend some time learning how to do this in Nyquist, then that is something that we can help with (could be very useful for you as a scientist :wink:) You will find a list of useful reference material for Nyquist in this forum post: Manuals and reference material

Also see Audacity Forum - Importing text as audio/exporting audio as text.


Gale

Keep the points positive.
They are just the frequency.
Use the nyquist prompt to produce a envelope signal from those.
(Select some audio in a mono track and go to Effects–>Nyquist Prompt)
The first part would be:

(setf control-signal (snd-from-array 0 8
   #( <your pasted data without commata> )))

the signal will start at 0 (the selection start) and have a sample rate of 8 times per second
The final sine wave will therefore last for 13.375 s (with 107 points), no matter how the selected audio is.

Let’s try it with random values as input:

;; white noise as input data
(setf control-signal (snd-white 0 8 (/ 107 8.0)))
;; bring from -1/1 into an appropriate range
;; 200 Hz to 4200 Hz in this exampple
(setf control-signal (sum 2200 (mult 2000 control-signal)))
(abs-env (scale-db -18 (hzosc control-signal)))

You would replace the first line after the comment with the code from the very beginning to do the same with your observations.

It might be necessary to smooth the data with a sliding average.
We also resample to 2205 Hz (for a track sample-rate of 44100), as this control-sample-rate is better suited for the calculations.

;; white noise as input data
(setf control-signal (snd-white 0 8.0 (/ 107 8.0)))
;; smoothing
;; 'smooth-over' observations are averaged to get a new data point
(setf smooth-over 11)
(setf total-len (truncate (snd-length control-signal ny:all)))
(setf mean 
   (sref
      (snd-avg control-signal 
       total-len total-len op-average) 0))
;; new sample rate
(setf old-sr (snd-srate control-signal))
(setf control-signal (force-srate *control-srate* control-signal))
(setf smooth-over (round (/ (* smooth-over (snd-srate control-signal)) old-sr)))
(setf control-signal 
   (sum mean 
      (snd-avg (diff control-signal mean) 
       smooth-over 1 op-average)))
;; bring from -1/1 into an appropriate range
;; 200 Hz to 4200 Hz in this exampple
(setf control-signal (sum 2200 (mult 2000 control-signal)))
(abs-env (scale-db -18 (hzosc control-signal)))

Change the first ‘(setf smooth-over…’ to control the smoothing (1 = none)
You have also to adapt the second last line to bring your values into the right range. We calculate here with min and max instead of the mean.
However, the variable ‘mean’ could also be used in this context.

Thanks for trying to help me, folks.

So far, I’m not coming up with much.

I’ve tried pasting in code, as suggested, into the Nyquist prompt. I tried just pasting in the first 5 data points"

(setf control-signal (snd-from-array 0 8
(0.590 0.612 0.653 0.723 0.788)))
(setf control-signal (sum 2200 (mult 2000 control-signal)))
(abs-env (scale-db -18 (hzosc control-signal)))

Clicking on “OK” I received an error message (“error: bad function - 0.59”, plus a lot more) and the admonition, “Nyquist did not return audio.”

If I convert those data points to ASCII numeric, I still get the same error and admonition.

Thanks again…
(setf control-signal (snd-from-array 0 8
(048 046 053 057 048 032 048 046 054 049 050 032 048 046 054 053 051 032 048 046 055 050 051 032 048 046 055 056 056 013 010)))
(setf control-signal (sum 2200 (mult 2000 control-signal)))
(abs-env (scale-db -18 (hzosc control-signal)))

Obviously, at this point I don’t understand the code very well, so don’t really know what I’m doing.

Incidentally, in order to activate the Nyquist prompt, I had to have some data imported. So, I imported a file, saved by Notepad in the format “Unicode big endian” which I gather is a 16-bit format, and that file doesn’t do much (see screen shot)
window002.png

What Robert has described is an alternative approach to the question of converting data to tones.
Rather than making tones out of the actual data, he is using the data to control the pitch of a generated tone.

What Robert has described is one approach - take the value of the data point to determine the pitch.

Taking a simple example, if your data is scaled for value between, say 100 and 1000, then you could generate a tone between 100 Hz and 1000 Hz.
In Nyquist, this could be written as:

(setq datapoint 440) 
(hzosc datapoint)

In the above example, the “single point of data” is “440”. We then use that value to generate a tone (using the “hzosc” function) at that frequency.

Coming back to your original idea, here is a bit of code (below) that takes a series of values as a list, where the (arbitrary) values that I have chosen are:
0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 -0.1 -0.2 -0.3 -0.4 -0.5 -0.6 -0.7 -0.8 -0.9 -1

The list of numbers is assigned as the value of “data”.

To use that data to create a sound, Nyquist has a function for converting “arrays” into sounds, so I have looped through the values of “data” and put those values into an array called “data-array”.

The final line is the Nyquist function “snd-from-array” which produces a sound from an array - in this case, the array contains our data (transferred from the “data” list.

Note that the sound returned by this script is very short - only 30 samples - you will need to zoom in very close to see it.

(setf data 
  (list 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 -0.1 -0.2 -0.3 -0.4 -0.5 -0.6 -0.7 -0.8 -0.9 -1))

(setf data-array (make-array (length data)))
(dotimes (i (length data))
  (setf (aref data-array i) (nth i data)))

(snd-from-array 0 *sound-srate* data-array)

There’s only a number sign missing before your data points which indicates that an array will follow (and not a list).

(setf control-signal (snd-from-array 0 8
  #(0.590 0.612 0.653 0.723 0.788)))
;; smoothing
;; 'smooth-over' observations are averaged to get a new data point
(setf smooth-over 1)
(setf total-len (truncate (snd-length control-signal ny:all)))
(setf mean 
   (sref
      (snd-avg control-signal 
       total-len total-len op-average) 0))
;; new sample rate
(setf old-sr (snd-srate control-signal))
(setf control-signal (force-srate *control-srate* control-signal))
(setf smooth-over (round (/ (* smooth-over (snd-srate control-signal)) old-sr)))
(setf control-signal 
   (sum mean 
      (snd-avg (diff control-signal mean) 
       smooth-over 1 op-average)))
;; bring from -1/1 into an appropriate range
;; 200 Hz to 4200 Hz in this exampple
(setf control-signal (sum 2200 (mult 2000 control-signal)))
(abs-env (scale-db -18 (hzosc control-signal)))

By the way, you can of course use your code example with the number sign (#) added.
The result will be “steppier” because the individual data points are not linearly interpolated.
The smoothed version will always go down at the end because the frequency is interpolated over 0.125 s from about 3000 Hz down to 2200 Hz.
Since there are no negative values in your observations, it will suffice to add e.g. 100 Hz instead of 2200. And the multiplier (2000) can of course be adjusted too.

The reason that I put the data into a list is because it is easy to convert text input (in a plugin) into a list of values. Thus it is easy to convert that code snippet into a plugin in which data can be simply copied and pasted into the plugin.

For short lists, it can be simplified to:

(setf input '(032 046 013))
(setf sound (snd-from-array 0 1 (apply 'vector input)))
(snd-display sound)

You can, and the simplest case for converting a single number (in this example “0.5”) to an audio sample in a track is:

(snd-from-array 0 1 #(0.5))

but I was wanting to move it on closer to writing a usable plugin.

Meantime the Skorko app at the end of the topic I mentioned seems to work fine if you have signed 16-bit text values (that is -32768 to 32767 where 0 is silence).

Gale