Outils pour utilisateurs

Outils du site


issue190:python

Ceci est une ancienne révision du document !


I have an embarrassing confession to make. Between issues with the weather here in central Texas, deadlines on book chapters, support and testing issues, I didn’t start thinking about this issue of FCM until February the first. I didn’t even get around to starting researching or writing the demo programs until the second. However, once I get this all written down, I truly believe I can hold my head up high and be proud of this issue’s article. Speaking of which, let’s get started. You might notice the title of this article. Yes, it DOES have to do with Python and missing beeps. For years, my computer has not had a built-in speaker, so the beep that I would normally rely on to get the user’s attention didn’t function the way I was used to. For a while, I tried in vain to get the blasted BEEP to respond, then something would come up and I’d have to abandon it for a while. Each time, the “a while” became longer and longer, and it eventually got lost in the shuffle. Until today.

I tried to go back – to find a way to get the bleep working – and still no luck. So I decided to give up on attempting to force a bleep tone out of my speakers, and started looking for an alternative method, one that would not force any of my users to have to install a ton of libraries before they could use a program of mine. I found a library named (of all things) Beepy. It is a pretty simple, lightweight library, and it has only one dependency. So I gave it a try. The instructions said to install it via pip; I’m lucky that my Python installs don’t require using a “3” at the end of my Python and Pip commands, so I forget that many of you aren’t quite that lucky. So if you can use just a “pip”, convert it mentally when you see “pip3”. pip3 install beepy I pulled up my ptpython REPL… The instructions say… import beepy beep(sound=1) That’s easy enough even for me to follow. Unfortunately, when I did that, nothing happened – other than an error message. Traceback (most recent call last): File “<stdin>”, line 1, in <module> NameError: name 'beep' is not defined name 'beep' is not defined »>

I double checked the documentation (what little there is), and the instructions still read the same. So I thought maybe there was a change in the program and the documentation didn’t get updated, so I scratched multiple body parts and decided I’d take a look at the output of the dir() function. »> dir(beepy) ['builtins', 'cached', 'doc', 'file', 'loader', 'name', 'package', 'path', 'spec', 'beep', 'make_sound'] There was the command beep, so I knew that there were simply two ways to make it work. I could try from beepy import beep beep(sound=1) And there it was! The sound of life! That is simple enough for me to include and code – if I wanted to get the user’s attention. But what about the other option? import beepy beepy.beep(sound=1) So, there it was, a teachable moment for us all. I read down the rest of the instructions and found the rest of the commands. sound argument takes either integers (1-7) or string (from the list below) as argument. Following are the mappings for the numbers: 1 : 'coin', 2 : 'robot_error', 3 : 'error', 4 : 'ping', 5 : 'ready', 6 : 'success', 7 : 'wilhelm' Hmmm. Only 7 options. I tried all 7 sounds and, while they worked, I wasn’t really impressed. Given the fact that the last release was in 2019, I thought there might be a better solution.

Since beepy had only one dependency, I thought I’d try looking at that for something easier and with a bit more options. That dependency is a library called simpleaudio. I ran a search on the Internet and got their github address. https://github.com/hamiltron/py-simple-audio. I was excited again. At least until I read the last release date. 2019. ARRGH! However, at least there was some documentation. There was still light at the end of the tunnel, and I prayed that it wasn’t a freight train. I called up the documentation url (https://simpleaudio.readthedocs.io/en/latest/) and started digging in. The first thing they show is a quick function check. Ok. I can do that. import simpleaudio.functionchecks as fc fc.LeftRightCheck.run() I thought I’d hear something like “Right speaker” coming out of the speakers, but I was happy to hear a piano note coming out of one of the speakers and, a moment later, the same note but an octave higher out of the other. I started to see some possibilities. I kept reading on. I moved through the documentation, seeing that, yes, there is a way to make simple beeps, but not so simply… But, I found what I knew had to be there. A way to play audio files, .wav files specifically. While I’m not a huge fan of .wav files, it would do the trick and, if I absolutely had to, there are plenty of websites that have free .wav files just for the purpose of embedding into someone’s program. So I tried it (I still had ptpython running). Code is shown top right.

I had just found one of those “meant to be embedded” files and downloaded it to my desktop. I copied the code block into the REPL and changed the example path to my real path and, low and behold, it worked as advertised. The first time! I dug in a bit deeper and found the other thing I wanted. The ability to create a simple tone or two on demand. However, the sample and this part of documentation was all about using numpy to generate the wave forms. This seemed to be a little bit more than I was willing to commit to, but I gave it a try anyway, since the REPL was still up (middle right). Well, that is very familiar to a guy who spent 6 years in the school band. If there were only two things I learned in High School, it was music and computers. So the three lines simply give the frequencies of a concert A4, a C# and an E. The next part sort-of made sense and I’m not a big numpy user. Basically, after setting the sample rate, and the duration of any note, numpy generates an array of values (bottom right). The next bit of code (below) creates numpy arrays containing the values for the sine waves of the three notes.

Then, this code (next page, top left) will “bundle up” all the notes and normalize them into a single numpy array. Then we feed the audio data into the playbuffer and wait for it to be done (top right). Knowing that I couldn’t have made any typos, since I copied the code directly from the documentation, I pressed the <enter> key after the last line, waiting to be presented with a three note chord, one note at a time. However, what I got was (below)… That was REALLY frustrating. I looked again at the code and I realized that the only floating point value was the value for T, which is the duration of the note to be played. That had to be in the np.linspace command. T = 0.25 t = np.linspace(0, T, T * sample_rate, False) Ah. The T * sample_rate portion is going to return a floating point value so I tried casting it to an integer. t = np.linspace(0, T, int(T * sample_rate), False)

When I ran the program again, I was presented with the three tones that I was expecting all along. There were other examples from the website that I tried; one of which creates a higher resolution object; and then I tried to add more notes to the whole process. The idea was to create all the notes from A4 to A5. I modified the base file to calculate all thirteen notes. # calculate the frequencies A_freq = 440 Ash_freq = A_freq * 2 ** (1 / 12) B_freq = A_freq * 2 ** (2 / 12) C_freq = A_freq * 2 ** (3 / 12) Csh_freq = A_freq * 2 ** (4 / 12) D_freq = A_freq * 2 ** (5 / 12) Dsh_freq = A_freq * 2 ** (6 / 12) E_freq = A_freq * 2 ** (7 / 12) F_freq = A_freq * 2 ** (8 / 12) Fsh_freq = A_freq * 2 ** (9 / 12) G_freq = A_freq * 2 ** (10 / 12) Gsh_freq = A_freq * 2 ** (11 / 12) A5_freq = 880 If you aren’t a musician, this might not mean much to you; however, I’ll try to give you a point of reference. I grabbed a screenshot of the virtual piano keyboard from an app on my computer, and added some hints. Each of the white keys are called natural keys. In the image, it starts with middle C (which is C4) and goes up an octave (13 notes) to C5. It’s called middle C because it’s the C key in the middle of the keyboard.

The black keys are sharps or flats depending on if you are going up the scale or down. So the keys are C, C#, D, D#, E, F, F#, G, G#, A, A#, B and back to C …for a total of thirteen tones. In “modern” music, this is known as an equal-tempered scale. It hasn’t always been this way, but that gets into a whole can of worms, so I won’t go there. Just know that the formulas above to generate the thirteen tones work well. You can find the program to do this as tones4.py in the repository. I was going to close the article here, with a note that I hadn’t tried to save the audio objects created to files, either as a numpy file that can be loaded later or as a .wav file that would negate the need to import numpy in every program you create and use this, just the simpleaudio package. Well after taking a quick break, I figured out how to do it, so I’ll let you in on the secret. The solution to saving the audio object to a file is really rather simple. At the bottom of the file, there are just two lines. In the case of tones4.py, add at the bottom of the file the following lines. # save the audio object to local file for future use with open(“scale1.npy”, “wb”) as f: np.save(f, audio)

That’s it. I’ve saved the program as tones4a.py . The file that was generated can be used again in a different program (top right). This solution, however, still requires having numpy as a dependency in the program. For me, the better solution is not only to have the numpy file (for future use) but to save the audio object to a .wav file. This requires the additional import of the wave package. Add that to the top of the test4 program, and then add the following code at the bottom. obj = wave.open(“scale1.wav”, “w”) obj.setnchannels(1) obj.setsampwidth(2) obj.setframerate(sample_rate) obj.writeframesraw(audio) obj.close() This will create a .wav file (without the need to save the numpy data if you don’t want to). Then to play it back, all you need is this short bit of code. import simpleaudio as sa wave_obj = sa.WaveObject.from_wave_file(“scale1.wav”) play_obj = wave_obj.play() play_obj.wait_done()

So I modified tones4a.py to include not only the write to a numpy file, but the code to create the .wav file and play it back. I’ve also included the playback routine as a simple python file named play_my_wav.py which will play back the “scale1.wav” file. Of course, the numpy files and some sample .wav files are also in the repository. Well, it’s time to wrap this up for this month. Just know that looking for a simple thing like getting your computer to beep on command can easily lead you to a path where you might be like Alice, and “go down the rabbit hole”. I’ve put the .wav file for Beepy as well as the test file for simpleaudio in my github repository. I’ve also included a few other test files I created for simpleaudio testing. You can find the repository at https://github.com/gregwa1953/FCM-190. Until next time, as always; stay safe, healthy, positive and creative!

issue190/python.1677503982.txt.gz · Dernière modification : 2023/02/27 14:19 de d52fr