AmI – a smart music composer (?) AI example on STM8

Update 20 Giu 2022: copied here
https://hackaday.io/project/185920-ami-a-smart-music-composer

A lot of (nerd) humour in this title 🙂 It can be read in two ways. The first is: “Am I a smart music composer ?” (the answer is: no) and the second is: “Artificial musical Intelligence – a smart music composer”. In practice, the purpose of this small project is to test an algorithm able to randomly generate a “decent” music. It could be easily done with a PC, using its huge HW / SW resources, but I want to do it with a very small microcontroller circuit based on STM8S001J micro, using my own firmware written in C language.

The software project is splitted in two parts: the first is “the player”, the part that just plays a melody stored in the microcontroller’s memory; the second is the algorithm that generates such melody in a random way. Let’s start from the first part, the “music player”.

First of all, we must build the circuit and test the music player firmware. The schematic is very simple (see below)

Differences from the prototype picture: R6 is 330R on the prototype and R3 was added to reduce sound level while recording the song with PC audio software.

You can download the schematic in PDF format from this link. The circuit is very simple and can be easily built on a prototype board. The piezo buzzer LS1 has low audio volume (it’s just a local “monitor”), but you can take the audio signal from J2 connector and send it to an amplifier. I decided to create a very small ADSR (Attack-Decay-Sustain-Release) in order to have a sound like a “ding” and not the usual “beep”. The I/Os PC5-PC4-PC3 (pin 7) are used all together to give the current necessary to charge the 100uF capacitor used in ADSR. The envelope of such ADSR circuit has the shape that you can see in the next image. There is a screenshot of the Audacity open-source software used to record the default song “Happy Birthday to you” produced by the test firmware.

If you want to hear such recording, try this:

Here is how such “song” is stored in the microcontroller’s memory. In this first section, the notes and their durations are stored as a constants array (FLASH memory), but in the final version the random song will be generated in a RAM array. The player, however, will act in the same way. The duration of every note is expressed in 50mS steps. The note “0” is a pause.

If you want to build this circuit, and improve or modify the first version of the firmware, click the link AmI for the full project folder, with C sources, ready to be edited, compiled and debugged using the free Cosmic-C and STM tools (read my previous articles on this subject). The working folder is zipped, so use the 7Z program with password eficara to extract it. The two buttons “like” and “unlike”, in this test firmware, are used just to start and stop the sequence. Note that the stop button is sampled only at the end of a note, so you must press and hold it down up to the end of a note to stop the song.

The algorithm and how it improves itself

Artificial musical Intelligence: what do we need to create a machine that can autonomously produce an acceptable music or melody ? First of all, we must implement a random numbers generator. This is the heart of the algorithm and simulates that magical element that we have no control over, like fate or destiny. After this, we have to set some rules that will be picked up randomly and used to generate a sequence of notes and pauses. Finally, the most important element: the feedback. The machine needs a feedback to improve itself. The feedback, in this case, is supplied by the human that listens to the music (you) via two buttons. Such buttons are labelled “like” and “unlike”. After listening to the music, you can press “like” if you appreciated the composition, or “unlike” if it was nearly horrible; or, you can simply refuse to press any button to say “not bad, but nothing special”. The result of such feedback is that the implemented rules will change their importance. All the rules, at beginning, start with the same importance, so all of them have the same probability to be extracted by the fate (the random number generator). If we press “like” after listening to the freshly composed melody, the rules involved in such creation will increase their importance, so they will have more probability to be used in the next composition. As opposite, if you press “unlike”, all the used rules will decrease their importance, so the probability to be used in the next composition is reduced. Well, what happens if one rule reaches the “zero” probability (in practice, if it can’t be extracted anymore)? Well, in such case the system will generate a new random rule that will substitute the one just erased. The new rule will start with a probability of 50%. Note that the new rule is really very “casual” and may contain some bad notes combination (or a touch of genius, who knows). All the probabilities of the rules and the rules themselves are stored in the non-volatile memory of the micro (Eeprom) in order to be available after a new power-on of the device. The device, when powered-on, will start composing a new melody (this is a very fast process), then will start to play that melody. After playing the whole melody, the program will wait 5 seconds for a feedback (button pressed) then will restart from the beginning, with a new melody. When you are tired to listen to such melodies, just turn OFF the device…

The rules

Rules are important. They permit to follow an ordered path in the chaos induced by random numbers. Here is an example of the first melody generated by the circuit using just the random numbers generator, without any rule:

In this test project, the rules are 8. The random number generator selects one of such rules for 8 times. At end of this operation, the resultant melody will be played. Every rule contains four “events”. An event is a note or a pause. The rules are arbitrarily chosen by me and I’m not a musician ! Anyway, when one of my initial rules will reach a zero probability, it will be regenerated by random numbers, so my own fingerprint will disappear in a short time. Every event is expressed in 4 bits. These are all the values:

0 = pause               8 = repeat last note
1 = 1 semitone over     9 = 1 semitone under
2 = 2 semitones over   10 = 2 semitones under
3 = 3     "      "     11 = 3     "       "
4 = 4     "      "     12 = 4     "       "
5 = 5     "      "     13 = 5     "       "
6 = 6     "      "     14 = 6     "       "
7 = 7     "      "     15 = 7     "       "

When there is a new event, it is applied starting from previous one (so, it’s a displacement). For example, if last note played was F and the selected value is 4, the new note played will be A (higher frequency); if the value is 9, the new note will be E (lower frequency) and if the value is 8, the new note will be equal to the last played note, so F again. If the value is 0, the new note will be a pause, so no sound, but the value of last played note will be retained in order to be used in next event. With 4 events for rule and each event defined in 4 bits, we  need one word (16 bits) for rule, but one note doesn’t have just a frequency, but also a duration. So there is a second part of the rule that contains such information. The duration just requires 2 bits, so the 4 events are contained in a single byte. These are the available duration values:

00 = 1/8 (4 * 50ms)
01 = 1/4 (8 * 50mS)
10 = 2/4 (16 * 50mS)
11 = 1/4 + 1/8 (12 * 50mS) 

The rules are stored in Eeprom, starting from address 0x4010. Here is an example of the Eeprom area mapping: (notes in yellow, duration in blue)

In order to test the rules, I used a program written by me in Visual Basic Express 2005 for Windows, many years ago. This is the screenshot of the application:

If you want to try with this small application, download the midimio.zip file from my website (look at the page PC Music). It’s a very small and simple program, but it is enough to write simple melodies. To compose a melody, use the checkboxes to select the note and the duration, then click the red button to save the event. The arrows at the sides of the red button move back and forward in the melody. When all the melody has been entered, you can play it with the buttons at the top right of the screen (play, stop, begin, end). The musical instrument can be changed modifying the value in the box “Strumento“. Your PC must be able to play MIDI, to use this program. Note that there are no editing functions ! If you want to modify an event, just use the arrows to move to such event and replace it with a new one, selecting it with the checkboxes, then click the red button. To clear all, restart the program. You can save / reload melodies using the “File” menu tab.

Probabilities

I said that the algorithm changes the probabilities of the rules. I used a simple way to do this. Imagine a dice: it has 6 faces with 6 numbers, so the probability for each number is 1/6. What happens if I modify one face changing its value from 1 to 5, for example ? If I am in Las Vegas, probably I will finish my days in jail ! No, no. This is just an example… In this case the probabilities of the number 5 are 2/6 (and 0 for the number 1) so, even though the dice still have 6 faces, we modified the probabilities of the numbers. In this program we have the numbers from 0 to 7 repeated 4 times, and our “dice” has 32 faces. The random numbers generator (RNG) gives us a number from 0 to 31 and the program checks if the selected “face” is present (bit at 1) or absent (bit at 0). If absent, the RNG is called again, until it finds a 1 bit. So, the rules with more bits at 1 have higher probabilities to be extracted. Here is an example:

Initial status: the rules from 0 to 7 all have two bits each set, so all of them have the same probability (2 of 4, or 50%)

bit position 33222222 22221111 11111100 00000000
             10987654 32109876 54321098 76543210
bit status   11111111 11111111 00000000 00000000

Now, suppose we have pressed “unlike” after a melody that used the rules 6, 2 and 0. In such case, the table will be modified as:

bit position 33222222 22221111 11111100 00000000
             10987654 32109876 54321098 76543210
bit status   10111010 11111111 00000000 00000000
              ^   ^ ^

It’s evident that the rules 6,2 and 0 now have less chances to be extracted. Another example here: we pressed “like” after a melody where rules 7,4 and 1 have been used. The table will be modified as follow:

bit position 33222222 22221111 11111100 00000000
             10987654 32109876 54321098 76543210
bit status   10111010 11111111 10010010 00000000
                               ^  ^  ^

In this case, the rules 7,4 and 1 have more chances to be extracted. So, every “like” and “unlike” will change the probabilities of the rules of 1 of 4, so 25%. The initial values for the rules are set to 2 of 4, or 50%. When all the bits for a rule are filled (all at 1), no additional increment can be done, while if all bits for a rule are erased (all at 0), a new rule will be randomly generated, with 50% of probability (2 of 4 bits). The 4 bytes (32 bits) containing this table are stored, like the rules, in non-volatile memory (Eeprom).

Random Numbers Generator

There are many algorithms to generate (pseudo) random numbers. In this experiment I used a very simple one, taken from an IC used in some old keyboards: the MM5837 Digital Noise Source. You can easily find the datasheet on the Internet.The operation principle is simple. There is a 17 bits shift register and the input is computed with a simple xor of the bits 17 and 14. One mandatory rule is that the initial value of the shift register MUST be different from zero. Here is my personal version of the algorithm:

As you can see, I used two 16 bits shift registers slrand[0] and slrand[1] with two different feedbacks. The first uses bits 14 and 11 as shift input, while the second uses bits 15 and 10. As additional randomness, I used the last bit of previous generated random value to select which register must be used to update the output. The function doesn’t return a value, but just modifies the global variale rand that is used in various parts of the program. The function is continuously called in the main loop of the program to randomize the rumber more often. The output range of the byte rand is 0 to 255.

The shift registers must be initialized with a non-zero value. I used this small function for the initialization. The function is called the first time one button is pressed to start the song generation and takes the seed from the timer TIM4, that is started immediatly after the reset.

End of job: results

After many tests, the music produced by the machine is still bad (in my opinion). May be the music needs some “magic” part that isn’t available on logic circuits, or the algorithm must be improved. Here are two tables containing the rules and the probabilities at the first launch of the program  and after some feedback given by me pressing the “like” or “unlike” buttons (rules in yellow, probabilities in blue):

Initial condition of Eeprom
Eeprom modified by the algorithm

If you want to try by yourself, load the zipped file AmI_2.zip, decompress it using the 7Z program with password “eficara” and then you will have the full project folder, with the C sources and the .S19 files. In the sub-folder named “release” there are the files: AmI.s19 to burn the program area of the microcontroller and the file rules.s19 to burn the eeprom area of the micro. If you want to start from more advanced rules and probabilities (based on my feedbacks), use the third file newrules.s19 to burn the eeprom area. The option bytes are untouched, leave them as they are in a new STM8S001J device.

Look at the main program, executed after reset:

Once started, the program makes a special sound high-to-low and low-to-high (Glide) to inform you that is waiting for a key pressure. When you press a key, the random number generator is initialized and the main starts, generating a new melody. Look at the SongPlayer state machine and what happens when the generated song ends:

If you press PLUS1 (like), the program calls the routine that increments the probabilities for the used rules (UpgradeProbs). If you press MINUS1 (unlike) the called routine is the one that diminishes the probabilities of the rules used (DowngradeProbs) and eventually generates a new rule if the probabilities reached zero. Finally, if you don’t press any key in the 5 seconds time, the probabilities are not modified. Note that if you press both the keys, you will have first an increment, then a decrement… I did it just for testing purposes. You can use the swim interface to read the eeprom and observe the variations in the rules and in the probabilities after giving your feedback (the micro will be reset).

Here is a melody generated with the eeprom data taken from the file newrules.s19 :

Obviously, the melody will be different if you run your own device, ‘cause it’s a collection of random rules. Have fun experimenting ! Bye.