Lovers of all the old, but incredibly interesting, good evening!

Remember this phone - Nokia 3310? Of course, remember! And such a thing as a melody synthesizer in it? Remember too, great. Do you miss old, warm and lamp tunes? So I miss you. And I also came across a sitik with more than a hundred sheet music for this editor. And that I had to leave this beauty without attention? No, really. What I've done? Right! I took and wrote the exact same melody generator that allows you to get a Wave file with a melody at the output. I wonder what came out of it? Then I ask for cat.
Nokia Composer was built into a whole bunch of phones like the Nokia 3310. In addition to 7 notes, it allowed you to record 5 sharps, specify an octave and duration in parts. And there were also notes that did not sound - pauses. That is, the “note” in Composer was really a note.
')
The very recording of the note for Composer looked like this:
That is, at the beginning there is a
duration (in parts from the whole), then a
point could be present, extending the sound one and a half times,
the note itself in letter designation, and the
octave . In this case,
after the pause, the octave is not indicated (logical?), And the duration is indicated exactly the same as for a normal note.
Okay, talk a lot.Let's write a script that will take the note as it is and return a tuple of parameters.
(we write in Python 2.7, yes)
def Parse_Tone(Note): Note = Note.upper() if Note.find("-") == -1: try: (Duration, Octave) = re.findall(r"[0-9]+", Note) except: pass else: Duration = re.findall(r"[0-9]+", Note)[0] Octave = 1 Tone = re.findall(r"[AZ,#,-]+", Note)[0] Duration = int(Duration) Octave = int(Octave) if Note.find(".") != -1: Duration = Duration/1.5 return (32/Duration, Tone, Octave)
In! That is, at first we translate it into the UPPER REGISTER, and then with regular expressions we parse it into components. Separately, we check the presence of a point (we increase 1.5 times) and take into account the pause.
Gototo!
Now, if we transfer functions, for example, 16C2, at the output we get (2, C, 2), that is, the duration in shares, note and octave.
What? Where did the number 32 come from? It's simpleThe original Nokia Composer allowed to set the duration of a note as
1/32 of a “full” note. Moreover, for it
there are also 1/16, 1/8, 1/4, 1/2 and 1 duration. That is, each next duration differs from the previous exactly 2 times. Then we can do this:
Take 1/32 notes as a “single note” . Then 1/16 is already 2 single notes, 1/8 - 4 and so on. Then we can take and divide 32 by the resulting duration.
With this sorted out. Now it remains to understand how we will turn the whole thing into a Wav file.If very roughly - in the
Wave file, in addition to the title, the voltages that are fed to the speaker are recorded. If a little more precisely -
part of the maximum voltage . That is, if the number 32765 is written in a two-byte frame, this means that you need to apply the maximum voltage. By changing the levels of stress over time, we can achieve oscillations of the membrane dynamics. And if these fluctuations are in the range we need ... That's right! We will hear the sound of a certain frequency.
Now, how to do it.Let's strain the memory and ... remember the school physics course! About the part that deals with
harmonic oscillations .
If it is very simple:
harmonic oscillations are a type of oscillations whose oscillating magnitude changes according to the law of sine (well, or cosine, as you wish)

The general formula of this disgrace looks like:

In this case, the cyclic frequency is
Remember? Fine! Now we need to understand - why.Since we decided to set the sound as a change in the voltage on the speaker, then the change will be set as a
sine wave with the cyclic frequency we need (by the way, the most obvious way to form sound). In this case, the formula for calculating the amplitude of the current frame will look like
Result = (32765 * VOL * math.sin (6.28 * FREQ * i / 44100))Where did all this come from? I tell.
32765 - We have a two-byte frame, so the maximum amplitude value is exactly 32765. VOL is the variable that sets the volume. Changes in the range from 0 (complete silence) to 1 (yelling like on the square)
6.28 is only 2 * Pi. You can calculate each time, but we are not animals.
FREQ - And this is the reason why everything was started - the frequency we need.
i / 44100 - time, relative to the origin. Why do we divide by 44,100? And because this is the sampling rate of the output file (well, I thought of it this way. It can be less. The quality will be lower). In a second there are 44100 counts, so we divide. I hope it turned out to explain
Here you go. One frame we have learned to ask. Now you need to make it all work. That is, in addition to the frequency, set the duration.
And since the frequency is fixed ... Aha! Wrap the loop.
Here in this. for i in range(0,TIME/10*441): Result = (32765*VOL*math.sin(6.28*FREQ*i/44100)) Frames.append(Result)
Again incomprehensibility. Where did TIME / 10 * 441 come from? From my imagination. No seriously. This is how I decided that the minimum playing time is 0.001 seconds . As I already said, one count (at a given sampling rate) is 1/44100 seconds. Accordingly, 0.001 seconds is 44.1 counts. A 44.1 = 441/10. And if you need to set N milliseconds ... multiply, yeah. So we get what we wrote (TIME is just the same time in milliseconds, yes)
So, let's wrap up the whole function, I hope no one is against it?
def Append_Freq(VOL,FREQ, TIME): for i in range(0,TIME/10*441): Result = (32765*VOL*math.sin(6.28*FREQ*i/44100)) Frames.append(Result)
In! Now we can generate sound of absolutely any frequency.
It remains to record what happened in the wave file.To work with Wave in Python (at least in 2.7) there is an adorable module with an unforgettable name -
Wave . And to work with all sorts of structures -
struct (in general, up to a certain point, Python is an insanely logical language).
After some dancing with a tambourine and other perversions, this function came out:
def Write_Wave(Name): File = wave.open(Name, 'w') File.setparams((1, 2, 44100, 0, 'NONE', 'not compressed')) Result = [] for frame in Frames: Result.append(pack('h', frame)) for Each in Result: File.writeframes(Each)
(I will not talk about it, because firstly everything is clear, and secondly - we will not move away from the topic)Here you go. Now you can generate a sound!
We try. Frames = [] Append_Freq(1, 4000, 5000) Write_Wave('Sound.wave')
Full volume, 4 kilohertz, 5 seconds.
Let's see what happened?This is how it sounds:5000Hz.wavThis is how it looks like:
Well, in general - what they wanted, they got it. The sound is really quite unpleasant.
By the way, if my memory serves me, that in the old library for Turbo Pascal the sound was set not by a sinusoid, but by a meander. In fact, simply change the voltage on the speaker. Just a sine wave is prettier than a meander or saw.Here you go. Now we have a function that generates the sound of the desired frequency and duration, and a function that records what we have done in this file.
Now you need to learn how to write notes.The pure (instrumentally colored)
note is the sound of a certain frequency.Clean Note Sharp is a sound that is
half a tone higher than a clean note.Be - mol - sound with a frequency of half a tone
below the pure notes . Be - moth the original Composer (still remember what we wanted to write there? Excellent!) Does not allow to ask, so we will not work with bells. Well them.
Octave - if simplified, is
a note frequency multiplier . That is, the frequency of Re of the second octave is twice as high as the same of Re of the first octave.
We find on the Internet the table of notes and their frequencies

And make a dictionary of it.
Here is this: Notes = {"-" : 0 ,"C" : 261.626, "#C" : 277.183, "D" : 293.665, "#D" : 311.127, "E": 329.628, "#E" : 349.228, "F" : 349.228, "#F" : 369.994, "G" : 391.995, "#G" : 415.305, "A" : 440.000, "#A" : 466.164, "B" : 493.883, "#B" : 523.251}
(In general, probably, it is more correct to write C #, not #C, but as a rule, all the tunes for Composer were specified in this format)And now let's write another function that generates a sound of a certain note. def Append_Note(VOL, TIME, NOTE, OCTAVE): for i in range(0,int(TIME/10.0*441)): FREQ = Notes[NOTE]*OCTAVE Result = (32765*VOL*math.sin(6.28*FREQ*i/44100)) Frames.append(Result)
So, here it is necessary to finish something else.
With the first part, everything is clear - the value of the desired frequency is taken from the dictionary, multiplied by an octave and written into the list.
Why do we need the second?
Very simple. If the desired duration is not a multiple of the sine wave period, then at time T1 a large voltage can be applied to the speaker, and nothing will be fed to T1 + 1. In my bearish rumor, it sounds like a suddenly-cut phrase from a murdered comrade is unpleasant. Therefore, we bring our sine wave to the nearest zero . With a high sampling rate, it will be noticeable little, and to the ear it will look like the same terminating phrase of a comrade, if a dead (but screaming) comrade falls into the well in his eyes. It is also not God knows what, but to generate Nokiev melodies will fit.
It now remains to write a function that will take a list of notes and feed it to the generator element by element. def Append_Notes(VOL, LIST, BPM): for Each in LIST: (Duration, Tone, Octave) = Parse_Tone(Each) try: Append_Note(VOL, int(Duration*1000*7.5/BPM), Tone, Octave) except: print "! %s" %Each Append_Note(0, int(250*7.5/BPM), "-", 1)
Something like this.
Is something incomprehensible again? This is normal, I also do not understand anything , now let 's figure it out.
BPM is the number of beats per minute. Roughly speaking, this is the “game speed”. This BPM is equal to the number of quarter notes in one minute . That is, one quarter note should be played 60 / BPM seconds. And since we decided that the duration of a single note with us is 1/32 - this value is 60/32 * 4 / BPM = 7.5 / BPM . One quarter note sounds exactly 1000 milliseconds (composers have come up with something for some reason), and then this result is also multiplied by the number of such 1/32 notes.
When the function runs in the list Frame will be ready file, which will only write.
Well, since
I’m too lazy to write a GUI, I love console interfaces, we’ll write a sequence of notes that accepts this sequence, BPM and the name of the output file in the argument list and feeds the Append_Notes () function
def MakeTune(): if (len(Arguments)!=3): print 'ERROR!\n USAGE:\n Composer "Notes" BMP FileName\nExample:\n Composer "16c2 16#a1 4c2 2f1 16#c2 16c2 8#c2 8c2 2#a1 16#c2 16c2 4#c2 2f1 16#a1 16#g1 8#a1 8#g1 8g1 8#a1 2#g1 16g1 16#g1 2#a1 16#g1 16#a1 8c2 8#a1 8#g1 8g1 4f1 4#c2 1c2 16c2 16#c2 16c2 16#a1 1c2" 120 Music.wave' return 1 List = Arguments[0].split(' ') BPM = int(Arguments[1]) OutFile = Arguments[2] print "\nFile information:\n\n Note number: %s\n Tempo: %s BPM\n\nGeneration of amplitude..." % (len(List), BPM) Append_Notes(1, List, BPM) print "\nOk!\n\nWriting Wave File..." Write_Wave(OutFile) File = open(OutFile,'rb').read() Size = len(File) print "\n File size: %.2f MB\n Duration: %.2f c. \n\nAll Done." % (Size/1024.0/1024, Size/44100/2)
That's all.
Now it remains only to transfer the initial data to the program and pick up the finished melody.
Let's try?Notes16c2 16 # a1 4c2 2f1 16 # c2 16c2 8 # c2 8c2 2 # a1 16 # c2 16c2 4 # c2 2f1 16 # a1 16 # g1 8 # a1 8 # g1 8g1 8 # a1 2 # g1 16g1 16 # g1 2 # a1 16 # g1 16 # a1 8c2 8 # a1 8 # g1 8g1 4f1 4 # c2 1c2 16c2 16 # c2 16c2 16 # a1 1c2
We drive into the generator ...

And take the result:
output.wavIn my opinion not bad.
More examples? Easy!Anthem of the USSRUnder the blue skyAutumnChristmas melody (from the original 3310)Want to write yourself? Try it!
Here are the notes4d1 4g1 8g1 8a1 8g1 8 # f1 4e1 4c1 4e1 4a1 8a1 8b1 8a1 8g1 4 # f1 4d1 4d1 4b1 8b1 8c2 8b1 8a1 4g1 4e1 8d1 8d1 4e1 4a1 4 # f1 2g1
Here's the pace:
200Pass through the generator and see what happens (Someone may even know about it).
Generator scriptHope you enjoyed it!Yours sincerely, listening to the monophonic Mozart, GrakovNe