{"id":76,"date":"2019-09-24T21:31:24","date_gmt":"2019-09-24T21:31:24","guid":{"rendered":"https:\/\/synthnotes.ucsd.edu\/wp7\/?p=76"},"modified":"2019-09-30T18:32:04","modified_gmt":"2019-09-30T18:32:04","slug":"adding-the-usb-otg-midi-driver-polyphonic-framework","status":"publish","type":"post","link":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/2019\/09\/24\/adding-the-usb-otg-midi-driver-polyphonic-framework\/","title":{"rendered":"Adding the USB OTG MIDI Driver &#8211; Polyphonic framework"},"content":{"rendered":"<p>The micro USB port on the STMF4Discovery board is a OTG (&#8220;on the go&#8221;) USB port. This means it can function as both a slave or master USB port. To use it as a MIDI host (so we can use MIDI keyboard and fader controllers) we need a OTG adapter. Here is one from <a href=\"https:\/\/www.amazon.com\/UGREEN-Adapter-Samsung-Controller-Smartphone\/dp\/B00LN3LQKQ\/ref=sr_1_5?crid=2H98IA6CMD11X&amp;keywords=usb+otg+adapter&amp;qid=1569355707&amp;sprefix=USB+OTG%2Caps%2C202&amp;sr=8-5\">Amazon<\/a>. These are typically used to attach flash drives to Android phones. The STM32Cube has an example of USB Host and Device code in the Middleware folder. However, instead of using that to develop a class-compliant MIDI host, I have simply adapted\u00a0<a href=\"https:\/\/github.com\/MrBlueXav\">Xavier Halgand<\/a>&#8216;s USB MIDI host code to work with the current version of the STM HAL libraries. Xavier is the author of the wonderful <a href=\"https:\/\/github.com\/MrBlueXav\/Dekrispator\">Dekrispator<\/a> synthesizer for the STM32F4Discovery board.<\/p>\n<p>My MIDI framework with a basic (terrible sounding) polyphonic synthesizer can be downloaded here:\u00a0<a href=\"https:\/\/synthnotes.ucsd.edu\/wp7\/wp-content\/uploads\/2019\/09\/f4disco-midi1.zip\">f4disco-midi<\/a>. You should be able to to unzip, place it in your workspace folder, and import to STM32CubeIDE.<\/p>\n<p>You will see a number of things added to this project, including a struct and enum to support each synthesizer voice. Each\u00a0voice represents an independent sound generator, and these can become fairly complex. Our voice will be a simple, unaliased, sawtooth oscillator with a fixed ASR gain envelope. It requires the following for each voice:<\/p>\n<pre><b>typedef<\/b> <b>struct<\/b> synthVoice\r\n{\r\n<b>    int<\/b> active;\r\n<b>    int<\/b> note;\r\n<b>    int<\/b> velocity;\r\n<b>    float<\/b> volume;\r\n<b>    float<\/b> frequency;\r\n<b>    float<\/b> phase;\r\n} synthVoice;\r\n\r\n<b>typedef<\/b> <b>enum\r\n<\/b>{\r\n<i>    INACTIVE<\/i> = 0,\r\n<i>    ATTACK<\/i>,\r\n<i>    DECAY<\/i>,\r\n<i>    SUSTAIN<\/i>,\r\n<i>    RELEASE\r\n<\/i>} voiceState;<\/pre>\n<p>The active variable will indicate the various states that each voice can be in, these states are in the following enum. The remaining members of the synthVoice struct are synthesis parameters. Later you will see 16 synthVoices being allocated<\/p>\n<pre>synthVoice voice[16];<\/pre>\n<p>In the function main(), the USB MIDI driver is initialized, and then two functions are called in the main while loop. The function MIDIApplication() parses any MIDI received through USB, and the function USBH_Process(&amp;hUSBHost) maintains the USB connection &#8211; it allows plugging and unplugging of USB devices.<\/p>\n<pre>\/*## Init Host Library ################################################*\/\r\n USBH_Init(&amp;hUSBHost, USBH_UserProcess_callback, 0);\r\n\/*## Add Supported Class ##############################################*\/\r\n USBH_RegisterClass(&amp;hUSBHost, USBH_MIDI_CLASS);\r\n\/*## Start Host Process ###############################################*\/\r\n USBH_Start(&amp;hUSBHost);\r\n\r\n\/\/ Initialize and start audio codec\r\nBSP_AUDIO_OUT_Init(OUTPUT_DEVICE_HEADPHONE, 90, 48000);\r\nBSP_AUDIO_OUT_Play((uint16_t *)codecBuffer, 128);\r\n \/* USER CODE END 2 *\/\r\n\/* Infinite loop *\/\r\n \/* USER CODE BEGIN WHILE *\/\r\n while (1)\r\n {\r\n     HAL_Delay(1);\r\n     \/* USER CODE END WHILE *\/\r\n     \/\/ MX_USB_HOST_Process();\r\n    \/* USER CODE BEGIN 3 *\/\r\n    MIDI_Application();\r\n    \/* USBH_Background Process *\/\r\n    USBH_Process(&amp;hUSBHost);\r\n }<\/pre>\n<p>MIDI_Application() calls a function I wrote called ProcessMIDI(). This processes one MIDI message, identifying the byte code for the type of MIDI message, and copys the data from the message. I have implemented the parsing for &#8220;note on&#8221;, &#8220;note off&#8221; and &#8220;controller change&#8221;. Framework for other MIDI messages (status type), is included. To parse all of the types of MIDI message, you will need to refer to the <a href=\"https:\/\/www.cs.cmu.edu\/~music\/cmsip\/readings\/davids-midi-spec.htm\">MIDI Specification<\/a> (easily found online).<\/p>\n<pre>void ProcessMIDI(midi_package_t pack)\r\n{\r\n\tint i;\r\n\tuint8_t status;\r\n\r\n\t\/\/ Status messages that start with F, for all MIDI channels\r\n\t\/\/ None of these are implemented - though we will flash an LED\r\n\t\/\/ for the MIDI clock\r\n\tstatus = pack.evnt0 &amp; 0xF0;\r\n\tif(status == 0xF0)\r\n\t{\r\n\t\tswitch(pack.evnt0)\r\n\t\t{\r\n\t\tcase 0xF0:\t\/\/ Start of System Exclusive\r\n\t\tcase 0xF1:\t\/\/ MIDI Time Code Quarter Fram\r\n\t\tcase 0xF2:\t\/\/ Song Position Pointer\r\n\t\tcase 0xF3:\t\/\/ Song Select\r\n\t\tcase 0xF4:\t\/\/ Undefined\r\n\t\tcase 0xF5:\t\/\/ Undefined\r\n\t\tcase 0xF6:\t\/\/ Tune Request\r\n\t\tcase 0xF7:\t\/\/ End of System Exclusive\r\n\t\t\tstatus = runningStatus = 0x00;\r\n\t\t\tbreak;\r\n\t\tcase 0xF8:\t\/\/ Timing Clock (24 times a quarter note)\r\n\t\t\tHAL_GPIO_TogglePin(LD5_GPIO_Port, LD5_Pin); \/\/ RED LED\r\n\t\t\tbreak;\r\n\t\tcase 0xF9:\t\/\/ Undefined\r\n\t\tcase 0xFA:\t\/\/ Start Sequence\r\n\t\tcase 0xFB:\t\/\/ Continue Sequence\r\n\t\tcase 0xFC:\t\/\/ Pause Sequence\r\n\t\tcase 0xFD:\t\/\/ Undefined\r\n\t\tcase 0xFE:\t\/\/ Active Sensing\r\n\t\tcase 0xFF:\t\/\/ Reset all synthesizers to power up\r\n\t\t\tbreak;\r\n\r\n\t\t}\r\n\t}\r\n\r\n\/\/ MIDI running status (same status as last message) doesn't seem to work over this USB driver\r\n\/\/ code commented out.\r\n\r\n\/\/\telse if((pack.evnt0 &amp; 0x80) == 0x00)\r\n\/\/\t\tstatus = runningStatus;\r\n\telse\r\n\t\trunningStatus = status = pack.evnt0 &amp; 0xF0;\r\n\r\n\r\n\r\n\tswitch(status)\r\n\t{\r\n\tcase 0x80:\t\/\/ Note Off\r\n\t\t\/\/ turn off all voices that match the note off note\r\n\t\tfor(i = 0; i&lt;16; i++)\r\n\t\t{\r\n\t\t\tif(voice[i].note == pack.evnt1)\r\n\t\t\t{\r\n\t\t\t\tvoice[i].active = RELEASE;\r\n\t\t\t\tkeyboard[voice[i].note] = -1;\r\n\t\t\t}\r\n\t\t}\r\n\t\tbreak;\r\n\tcase 0x90:\t\/\/ Note On\r\n\t\tif(pack.evnt2 == 0) \/\/ velocity 0 means note off\r\n\t\t\t\/\/ turn off all voices that match the note off note\r\n\t\t\tfor(i = 0; i&lt;16; i++)\r\n\t\t\t{\r\n\t\t\t\tif((voice[i].note == pack.evnt1) &amp;&amp; (voice[i].active != INACTIVE))\r\n\t\t\t\t{\r\n\t\t\t\t\tvoice[i].active = RELEASE;\r\n\t\t\t\t\tkeyboard[voice[i].note] = -1;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t\/\/ if this key is already on, end the associated note and turn it off\r\n\t\t\tif(keyboard[pack.evnt1] != -1)\r\n\t\t\t{\r\n\t\t\t\tvoice[keyboard[pack.evnt1]].active = RELEASE;\r\n\t\t\t\tkeyboard[pack.evnt1] = -1;\r\n\t\t\t}\r\n\t\t\t\/\/ find an inactive voice and assign this key to it\r\n\t\t\tfor(i = 0; i&lt;16; i++)\r\n\t\t\t{\r\n\t\t\t\tif(voice[i].active == INACTIVE)\r\n\t\t\t\t{\r\n\t\t\t\t\tvoice[i].active = ATTACK;\r\n\t\t\t\t\tvoice[i].note = pack.evnt1;\r\n\t\t\t\t\tvoice[i].velocity = pack.evnt2;\r\n\t\t\t\t\tkeyboard[pack.evnt1] = i;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tbreak;\r\n\tcase 0xA0:\t\/\/ Polyphonic Pressure\r\n\t\tbreak;\r\n\tcase 0xB0:\t\/\/ Control Change\r\n\t\tswitch(pack.evnt1) \/\/ CC number\r\n\t\t{\r\n\t\tcase 3 \t:\r\n\t\t\tbreak ;\t\/\/ tempo\r\n\t\tcase 7 :\r\n\t\t\tbreak;\t\/\/ master volume\r\n\t\t}\r\n\t\tbreak;\r\n\tcase 0xC0:\t\/\/ Program Change\r\n\t\tbreak;\r\n\tcase 0xD0:\t\/\/ After Touch\r\n\t\tbreak;\r\n\tcase 0xE0:\t\/\/ Pitch Bend\r\n\t\tpitchbend = pack.evnt2 * 128 + pack.evnt1;\r\n\t\tbreak;\r\n\t}\r\n}\r\n<\/pre>\n<p>My implementation of ProcessMIDI() doesn&#8217;t do voice allocation (though it does look for inactive voices before assigning a new one), and isn&#8217;t doing anything with the data from CC (controller change) messages. There is lots of room for improvement, this is just intended as a starting point.<\/p>\n<p>The audioBlock() audio callback function has been modified to generate up to 16 voices, each synthesizing a harsh sawtooth wave. The data set in ProcessMIDI() is being used to set the frequency of each voice, and to manage the ASR envelope in each voice. Note that when a note off is received by ProcessMIDI, the voice is not made inactive. Instead, the voice\u00a0is put in the RELEASE part of the envelope, and is only made INACTIVE when the envelope reaches zero.<\/p>\n<pre><b>void<\/b> <b>audioBlock<\/b>(<b>float<\/b> *input, <b>float<\/b> *output, int32_t samples)\r\n{\r\n<b>    int<\/b> i, v;\r\n<b>    float<\/b> increment1, increment2;\r\n<b>    for<\/b>(i = 0; i &lt; samples; i += 2)\r\n        output[i&lt;&lt;1] = output[(i&lt;&lt;1) + 1] = 0.0f;\r\n<b>    for<\/b>(v = 0; v &lt; 16; v++)\r\n    {\r\n<b>        if<\/b>(voice[v].active != <i>INACTIVE<\/i>)\r\n         {\r\n             voice[v].frequency = mtoinc[voice[v].note];\r\n<b>           for<\/b>(i = 0; i &lt; samples; i += 2)\r\n            {\r\n                voice[v].phase += voice[v].frequency;\r\n<b>              if<\/b>(voice[v].phase &gt; 1.0f)\r\n                    voice[v].phase = voice[v].phase - 1.0f;\r\n                output[i&lt;&lt;1] += ((voice[v].phase * 2.0f) - 1.0f) * voice[v].volume * 0.125f;\r\n                output[(i&lt;&lt;1) + 1] = output[i&lt;&lt;1];\r\n<b>              if<\/b>(voice[v].active == <i>ATTACK<\/i>)\r\n                {\r\n                    voice[v].volume += 0.02f;\r\n<b>                  if<\/b>(voice[v].volume &gt;= 1.0f)\r\n                        voice[v].active = <i>SUSTAIN<\/i>;\r\n                }\r\n<b>              else<\/b> <b>if<\/b>(voice[v].active == <i>SUSTAIN<\/i>)\r\n                    voice[v].volume = 1.0f;\r\n<b>              else<\/b> <b>if<\/b>(voice[v].active == <i>RELEASE<\/i>)\r\n                {\r\n                    voice[v].volume -= 0.0002f;\r\n<b>                  if<\/b>(voice[v].volume &lt;= 0.0f)\r\n                        voice[v].active = <i>INACTIVE<\/i>;\r\n                }\r\n            }\r\n        }\r\n    }\r\n}<\/pre>\n<p>This code should be useful as a starting point for synthesizer creation. Those creating drum machines or effects processors will want to handle MIDI messages differently.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The micro USB port on the STMF4Discovery board is a OTG (&#8220;on the go&#8221;) USB port. This means it can function as both a slave or master USB port. To use it as a MIDI host (so we can use MIDI keyboard and fader controllers) we need a OTG adapter. Here is one from Amazon. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-76","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/posts\/76","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/comments?post=76"}],"version-history":[{"count":4,"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/posts\/76\/revisions"}],"predecessor-version":[{"id":85,"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/posts\/76\/revisions\/85"}],"wp:attachment":[{"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/media?parent=76"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/categories?post=76"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/synthnotes.ucsd.edu\/wp7\/index.php\/wp-json\/wp\/v2\/tags?post=76"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}