📜 ⬆️ ⬇️

Forgotten music or a bit of Nokia 3310, PC-Seaker and MIDI file generation (P1)

Here we will talk a little about the melody format (RTTL) in the old phone models, about playing such melodies using the usual PC-Speaker of the computer, as well as about creating (generating) MIDI files. All my thoughts I will back up with Pascal code.

Immediately, I’ll make a reservation that this article most likely does not represent any practical interest for the majority and was created by the author rather for himself, as a memory so to speak ... But enough of the lyrics, let's begin.


I think that many still remember how, sitting on a gallery in a school or institute, the comrades of the form “8e, 16d, 16b4,16a4,16b4,8a4,16a4,16a # 4” were reprinted from their phone. This so-called RTTL format for recording ringtones. It makes no sense to describe it in detail here, since the description is fully accessible on the Internet , but for further understanding we will consider the simplest example. So, take this RTTL melody:

Simpsons: d = 4, o = 5, b = 160: 32p, c.6, e6, f # 6,8a6, g.6, e6, c6,8a, 8f #, 8f #, 8f #, 2g

As you can see, the format is

[name : duration , octave , speed per minute (BPM) : the melody itself].

We will need the following parameters:

“D = 4” The duration of the note by default. This means that in the recording of the melody itself, when we want to play a note “la” with a duration of 4, we will not necessarily have to record it as “ 4a ”. It is enough to write it simply as “ a ”. Here I note that if we compare the duration of the RTTL and the duration of the note in the musical sense, then there is a simple correspondence - 1 / duration of the RTTL. Thus, “d = 4” in the RTTL record means that we play the notes by default, with a duration of “one fourth”. If d = 6, then “one sixth” and so on.

“O = 5” Octave by default. From music theory, we recall that the octave is twice the frequency between the same notes. So, if the note “la” of the fourth octave has a frequency of 440 Hz, then the same note “la” of the fifth octave will have a frequency of 880 Hz. Exactly the same as with the duration, in the further recording we do not need to record the note a “la” of the fifth octave, as “ a5 ”. It is enough just to write it down as “ a ” and we will play the note “la” of the fifth octave with a duration of one fourth. Thus it turns out that in our case the entry “ a ” will be equivalent to the entry “ 4a5 ”.

“B = 160” . Pace, or speed per minute (BPM). To calculate the milliseconds that a note should sound, I used the following formula: ((60000 / b) / d) * 8, where b is our tempo and d is the duration of the note. Why so, I already confessed and forgot myself. But it works :)

Then the melody itself begins. From the description it follows that the format for recording notes is generally as follows:

[duration note octave add. sign].

Commas act as a separator between notes. Notes are used in the English (?) Recording system 'c', 'c #', 'd', 'd #', 'e', ​​'f', 'f #', 'g', 'g #', 'a', ' a # ',' b ', which corresponds to Italian' to ',' to sharp ',' re ',' re sharp ',' mi ',' fa ',' fa sharp ',' salt ',' salt sharp ', 'la', 'la sharp', 'si'. And also a pause - 'p'. Additional marks may be:
" . " (dot) - an increase in the duration of the note in half
" ; " (semicolon) - twice
" & " (ampersand) - 2.5 times
There should be no problem with this - when we find one of these signs at the end of a note, we will simply multiply the calculated milliseconds by ½, two and two and a half times.

We’ll finish with the theory, if you have any questions, once again I recommend reading the description of the RTTL format. We proceed to practice.

PC Speaker

For a start, we will output our melody for simplicity to the usual PC-Speaker. In most programming languages, to output audio to a speaker, we need to know only two parameters — the frequency and the delay in milliseconds. So, in Virtual Pascal there is a procedure
PlaySound ( Freq , Duration : Longint ) ;

which I will use.
Now let's think and start with a simple one. Suppose that our melody consists of only one note and looks like this: “test: d = 4, o = 5, b = 125: a”. Thus, we need to play the “la” note of the fifth octave with a duration of one-fourth with a tempo of 125. The first thing that comes to mind is that we need to know all note frequencies for all octaves, for this we naturally build a table of frequencies:
Frequency : Array [ 1..8 * 12 ] of word ; {frequency table}

{....... some code .......}

Procedure InitFreqTable; {Frequency Table Initialization Procedure}
HerzOfFirst = 32.703195258 ; {note C of the first octave}

tmpReal1 , tmpReal2 : real ;
i : word ;

tmpReal1 : = HerzOfFirst; {let's start with the first note C of the first octave}
tmpReal2 : = exp ( ln ( 2.0 ) / 12.0 ) ; {constant with which the frequency changes from note to note}
for i : = 1 to 8 * 12 do {12 notes per octave, 8 octaves}
Frequency [ i ] : = round ( tmpReal1 ) ;
tmpReal1 : = tmpReal1 * tmpReal2;
end ;

Fine. Further, the whole matter rests on the analysis of the recording of notes on the duration, the octave, and the note itself. I think it’s inappropriate to give here the parsing itself, if someone is interested in the end of the article there will be the full source code of the program. Let's analyze only the calculation in the table of the note itself. I implemented it like this:
{we previously parsed the duration and octave}
// parse note
{i - will denote the offset in the frequency table}
i : = ( octave - 1 ) * 12 ; {first move to the desired octave}
Case S [ 1 ] of {In s [1] there is a note symbol}
'c' : inc ( i , 1 ) ; {and add to note offset}
'd' : inc ( i , 3 ) ; {here we do not take into account sharps, I process them after}
'e' : inc ( i , 5 ) ;
'f' : inc ( i , 6 ) ;
'g' : inc ( i , 8 ) ;
'a' : inc ( i , 10 ) ;
'b' , 'h' : inc ( i , 12 ) ;
'p' : i : = 0 ; {and this is a pause}
begin {if another letter is encountered, give an error}
PlayRTTTLNote : = 1 ;
WriteLn ( 'DEBUG: Error [1]: Wrong note - "' , S [ 1 ] , '"' ) ;
end ;

So, now in the variable “i” there is an offset in the frequency array of the note we need. You can now process the “#” (sharp) character at the end, and if it is present in the note recording, simply increase the value of “i” by one. Now we can play a note, while with a duration of 1000 ms with a simple command:
PlaySound ( Frequency [ i ] , 1000 ) ;

With a note and frequency sorted out. It remains to deal only with the duration of the notes. Above, I already gave a formula by which I calculated the duration of the sound in milliseconds depending on the tempo and the duration of the note itself. Below just give the code:
// calculate microseconds
PrecalcMS : = ( 60000 / BPMspeed ) ; {BMPspeed is temp, i.e. the parameter “b =”}
PrecalcMS : = ( PrecalcMS / Duration ) * 8 ; {duration - the frequency of the notes}
MicroSecs : = round ( PrecalcMS ) ; {round to integer}

Here the main thing is not to forget about the additional values ​​of “dot”, “semicolon” ​​or “ampersand” that may be present in the musical notation, and depending on their presence, multiply the value of PrecalcMS by ½, 2 or 2.5. In the main program it is, I do not cite here. Everything, now we can lose the note with the frequency defined above and the duration we need:
Procedure PlayNote ( Note : byte ; MicroSecs : Word ) ;
If Note = 0 then {Remember, if we met “p”, then set I = 0}
Delay ( MicroSecs ) {this means a pause}
playsound ( Frequency [ Note ] , microsecs ) ;
End ;

And in the main program we play a note:
PlayNote ( I , MicroSecs ) ;

In such a simple way, we implemented the RTTL melodies player on PC-Speaker. The full source code of the program, as well as the compiled version, are available here: rghost.ru/2230394 I used to compile Virtual Pascal, but I think that I should get together in Free Pascal without any problems.

In the next article we will talk about creating an RTTL 2 MIDI converter in general and creating and working with MIDI files in particular. Have a good day!

When writing the article materials were used:

Source: https://habr.com/ru/post/100581/

All Articles