Differences
This shows you the differences between two versions of the page.
| test-and-measurement:analog-discovery-pro-3x50:midi [2021/10/06 15:54] – [Build and Connect the Circuit] Álmos Veres-Vitályos | test-and-measurement:analog-discovery-pro-3x50:midi [2022/10/04 11:38] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ====== Building a MIDI Controller with Analog Discovery Pro (ADP3450/ | ====== Building a MIDI Controller with Analog Discovery Pro (ADP3450/ | ||
| ~~TechArticle~~ | ~~TechArticle~~ | ||
| + | |||
| + | {{ : | ||
| <WRAP center round todo 60%> | <WRAP center round todo 60%> | ||
| Line 8: | Line 10: | ||
| <WRAP group> | <WRAP group> | ||
| - | |||
| - | |||
| A Musical Instrument Digital Interface (MIDI) controller is a key element in electronic music production. Such devices, both hardware and software, can be quite expensive, but with a bit of work, the Analog Discovery Pro can be set up as a highly customizable MIDI controller, with wireless connection capability. This guide presents, how to configure your Test & Measurement device and your computer to be able to send and receive MIDI commands over Ethernet or Wi-Fi. | A Musical Instrument Digital Interface (MIDI) controller is a key element in electronic music production. Such devices, both hardware and software, can be quite expensive, but with a bit of work, the Analog Discovery Pro can be set up as a highly customizable MIDI controller, with wireless connection capability. This guide presents, how to configure your Test & Measurement device and your computer to be able to send and receive MIDI commands over Ethernet or Wi-Fi. | ||
| </ | </ | ||
| Line 21: | Line 21: | ||
| * hardware user interface elements (buttons, switches, sliders, etc.) | * hardware user interface elements (buttons, switches, sliders, etc.) | ||
| * passive electronic components | * passive electronic components | ||
| - | * other... | + | |
| == Software == | == Software == | ||
| <WRAP group>< | <WRAP group>< | ||
| Line 39: | Line 39: | ||
| * WaveForms SDK - preinstalled | * WaveForms SDK - preinstalled | ||
| * [[https:// | * [[https:// | ||
| - | </ | + | </ |
| - | </ | + | |
| ---- | ---- | ||
| - | ===== Introduction ===== | + | ===== What is MIDI? What is RTP-MIDI? What is a DAW? ===== |
| - | ==== What is MIDI? What is RTP-MIDI? What is a DAW? ==== | + | |
| <WRAP group> | <WRAP group> | ||
| Musical Instrument Digital Interface (MIDI) is a technical standard which is used in electronic music production. MIDI controllers are not instruments, | Musical Instrument Digital Interface (MIDI) is a technical standard which is used in electronic music production. MIDI controllers are not instruments, | ||
| Line 71: | Line 69: | ||
| {{ : | {{ : | ||
| </ | </ | ||
| + | ---- | ||
| ==== Setting Up the Analog Discovery Pro as a MIDI Controller ==== | ==== Setting Up the Analog Discovery Pro as a MIDI Controller ==== | ||
| Line 77: | Line 75: | ||
| To successfully run this example, you will need the latest Linux version for your device. To update the ADP3450/ | To successfully run this example, you will need the latest Linux version for your device. To update the ADP3450/ | ||
| - | The first step is to connect your device | + | Now connect your device the internet. If you already have internet enabled for Linux mode, just connect the Ethernet cable and skip this step. Else, follow this guide: [[test-and-measurement: |
| </ | </ | ||
| - | --> Connect the Analog Discovery Pro (ADP3450/ | ||
| - | <WRAP group> | ||
| - | {{section> | ||
| - | </ | ||
| - | <-- | ||
| - | ---- | ||
| - | |||
| - | |||
| - | <WRAP group> | ||
| - | Now boot into Linux mode. If your device already is in Linux mode, just skip this step. | ||
| - | |||
| - | --> Booting into Linux Mode # | ||
| - | <WRAP group>< | ||
| - | Mode selection can be done through WaveForms using the Device Manager. With the Analog Discovery Pro connected to the host computer by Ethernet, open WaveForms. If the Device Manager hasn't opened yet, then click Settings -> Device Manager. | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | |||
| - | <WRAP group>< | ||
| - | Select the Analog Discovery Pro from the device menu, then click the Boot button. The Boot Mode window should open. By default, Standard Mode which allows the user to communicate with the instrumentation device over USB or Ethernet will be selected. | ||
| - | |||
| - | To change to Linux Mode, select Linux, then click "Apply & Reboot" | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | |||
| - | <WRAP group>< | ||
| - | At this time, an audible click should be heard and the Analog Discovery Pro will disconnect from WaveForms. About 10-15 seconds should pass before it reconnects. Confirm that the second column next to the Analog Discovery Pro says " | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | <-- | ||
| - | </ | ||
| - | ---- | ||
| - | |||
| - | <WRAP group> | ||
| - | Connect to the device with a terminal emulator through the internet. | ||
| - | --> Connect to the ADP3450 # | ||
| <WRAP group> | <WRAP group> | ||
| To command the ADP3450/ | To command the ADP3450/ | ||
| - | Tera Term will be used in this guide, but the steps are similar for each of the mentioned | + | Tera Term will be used in this guide, but the steps are similar for each of the mentioned |
| - | </ | + | |
| <WRAP group>< | <WRAP group>< | ||
| Line 134: | Line 93: | ||
| </ | </ | ||
| {{ : | {{ : | ||
| - | </ | + | </ |
| - | <-- | + | |
| - | </ | + | |
| - | ---- | + | |
| <WRAP group> | <WRAP group> | ||
| - | First, install | + | Install |
| < | < | ||
| Line 178: | Line 134: | ||
| The control signals can be divided in three main categories: digital signals (coming from buttons, switches and rotary encoders), slowly changing analog signals (coming from potentiometers/ | The control signals can be divided in three main categories: digital signals (coming from buttons, switches and rotary encoders), slowly changing analog signals (coming from potentiometers/ | ||
| - | Digital signals can bi directly connected in the driver circuit. They must be debounced (the mechanical noise created by the " | + | Digital signals can be directly connected in the driver circuit. They must be debounced (the mechanical noise created by the " |
| + | </ | ||
| + | <WRAP group> | ||
| Slowly changing analog signals can't be directly measured by the digital inputs of the ADP. While the analog inputs (oscilloscope channels) can measure them directly, they should be used to record fast changing analog signals, so another solution should be found. One method to transform the analog signals to digital ones is by using comparators: | Slowly changing analog signals can't be directly measured by the digital inputs of the ADP. While the analog inputs (oscilloscope channels) can measure them directly, they should be used to record fast changing analog signals, so another solution should be found. One method to transform the analog signals to digital ones is by using comparators: | ||
| </ | </ | ||
| - | < | + | --> Click for Model and Simulation # |
| + | <WRAP group> | ||
| + | < | ||
| + | </ | ||
| + | <-- | ||
| <WRAP group> | <WRAP group> | ||
| Line 189: | Line 151: | ||
| </ | </ | ||
| - | ==== PLD Design ==== | + | --> Click for Model and Simulation # |
| - | <WRAP group>< | + | <WRAP group> |
| - | As most of the control signals are digital (including the converted slow analog signals), the conditioning circuits for them will be also digital. To prototype a complex digital circuit, the easiest way is to use an FPGA development board. In this project the Arty-S7 FPGA board will be used, for which the design will be created in Multisim, to avoid using VHDL, or Verilog (which can be challenging, | + | <html><iframe width=" |
| - | + | </ | |
| - | If your chosen FPGA board isn't available in Multisim PLD design, follow the instructions in [[link_to_hackster_page|this]] guide to create the configuration files for it. | + | <-- |
| + | ---- | ||
| - | Start a new PLD design | + | ==== User Interface Design ==== |
| - | + | <WRAP group>< | |
| - | When ready, click //Transfer -> Export to PLD... -> Generate | + | Design the user interface circuit to contain the control and indicator elements you want. Keep in mind the offered possibilities |
| </ | </ | ||
| - | {{ : | + | {{ : |
| </ | </ | ||
| - | ==== Configuring the ADP3450 to Upload the Bit File ==== | ||
| <WRAP group> | <WRAP group> | ||
| - | {{section> | + | For a detailed presentation of the user interface circuitry used in this guide, you can check the drop-down below, or you can design your own custom user interface. |
| - | Now you can use the " | + | |
| - | </ | + | |
| - | ==== Build and Connect the Circuit | + | --> The User Interface |
| <WRAP group> | <WRAP group> | ||
| - | The controller | + | <WRAP group> |
| + | === Parts of the User Interface === | ||
| + | The user interface | ||
| + | * The most important control elements are the play/pause button, the record button, the volume slider and the channel selector button. To display the selected channel, this circuit part also contains a [[pmod: | ||
| + | * To further shave the sound of the controller, the second part contains three filters (low, middle and high). The analog-to-PWM converter for the volume slider | ||
| + | * In the third block, six potentiometers (with analog-to-PWM converters) are used to control the effects set in the DAW. | ||
| + | * While traditional keypads have more buttons, | ||
| + | * The actual drum pads are mechanical parts, each one a disk containing a piezo, but the driver | ||
| + | </ | ||
| - | --> The User Interface Circuit # | ||
| <WRAP group> | <WRAP group> | ||
| - | <WRAP group>< | + | === Connecting the User Interface === |
| - | Use buttons with pull-down resistors for functions such as **play/ | + | Connect the output signals of the user interface to the FPGA board, but keep track of the connections, as they will have to be defined in the PLD design later, in Multisim (see below). |
| - | </ | + | |
| - | {{ : | + | |
| - | </ | + | |
| - | <WRAP group>< | + | The BNC oscilloscope connectors are connected directly to the drum pad outputs |
| - | The voltages falling on the **volume**, **equalizer** | + | |
| - | </ | + | |
| - | {{ :test-and-measurement: | + | |
| - | </ | + | |
| - | <WRAP group>< | + | The Pmod connectors are used on the Arty-S7 to connect the user interface control elements, while the Chipkit analog headers |
| - | Rotary encoder modules | + | </ |
| - | </ | + | </WRAP> |
| - | {{ :test-and-measurement: | + | <-- |
| - | </ | + | </ |
| + | ---- | ||
| + | ==== Driver Design ==== | ||
| <WRAP group>< | <WRAP group>< | ||
| - | The keys of the "piano" | + | As most of the control signals are digital (including the converted |
| - | </ | + | |
| - | {{ : | + | |
| - | </ | + | |
| - | <WRAP group>< | + | If your chosen FPGA board isn't available in Multisim PLD design, follow |
| - | Certain parts of the controller will be lit by RGB LED lights, the color depending on the channel used. To realize the lighting, an LED strip is used, which is powered from 5V. The color is set by three PWM signals coming from the ADP, the certain color LEDs being driven by N-channel MOSFETs | + | |
| - | </ | + | |
| - | {{ :test-and-measurement:analog-discovery-pro-3x50:rgb_led_strip.png?600 |}} | + | |
| - | </ | + | |
| - | <WRAP group>< | + | Start a new PLD design in Multisim, select your board and create |
| - | The current MIDI channel | + | |
| - | </ | + | |
| - | {{ : | + | |
| - | </ | + | |
| - | <WRAP group><WRAP half column> | + | When ready, click //Transfer -> Export to PLD... -> Generate |
| - | As drum pads, piezoelectric sensors are used. These contain materials, which generate a voltage if their shape changes. The generated voltage is rectified | + | |
| </ | </ | ||
| - | {{ : | + | {{ : |
| </ | </ | ||
| - | ---- | + | < |
| - | </WRAP> | + | For a detailed presentation of the driver circuitry used in this guide, you can check the drop-down below, or you can design your own driver on an FPGA board. |
| - | <-- | + | |
| - | As there are too many control signals to connect them directly into the ADP, they have to be multiplexed. This multiplexing and also some signal conditioning can be done by a digital " | + | --> |
| - | + | <WRAP group> | |
| - | --> | + | |
| <WRAP group> | <WRAP group> | ||
| - | |||
| === Hierarchical Blocks === | === Hierarchical Blocks === | ||
| - | <WRAP group> | + | Before creating the driver, design and test certain circuits which will be used multiple times in the driver, to make your final design easier to create and to understand. |
| - | Before creating the driver, design and test certain circuits which will be used multiple times in the driver, to make your final design easier to create and understand. | + | |
| - | + | * debouncer | |
| - | <WRAP group>< | + | * latch - so buttons can be used as switches (on press turns the signal high, the next one turns it low). |
| - | The first hierarchical block created is the clock divider. Several | + | |
| - | </ | + | |
| - | {{ :test-and-measurement: | + | * pulse counter |
| - | </ | + | * 7 segment display decoder |
| - | + | * display selector | |
| - | <WRAP group>< | + | |
| - | One of the most important building blocks is the debouncer. When a button is pressed, some mechanical noise appears in the signal (the bouncing of the two metal plates on each other), so if the action is taken on the edge of the signal, the circuit will be triggered multiple times. A debouncer " | + | |
| - | </ | + | |
| - | {{ :test-and-measurement: | + | |
| - | </ | + | |
| - | + | ||
| - | <WRAP group>< | + | |
| - | A simple latch should also be implemented, | + | |
| - | </ | + | |
| - | {{ : | + | |
| - | </ | + | |
| - | + | ||
| - | <WRAP group>< | + | |
| - | A 1:2 demultiplexer | + | |
| - | </ | + | |
| - | {{ : | + | |
| - | </ | + | |
| - | + | ||
| - | <WRAP group>< | + | |
| - | The next block to be prepared is an up-down counter to N (0< | + | |
| - | </ | + | |
| - | {{ :test-and-measurement: | + | |
| - | </ | + | |
| - | + | ||
| - | <WRAP group>< | + | |
| - | As the Python script will use polling to measure every signal, it might miss certain events of the rotary encoders, so they should be re-encoded as numbers. This is why a pulse counter is designed, which is an up-down counter clocked by the two waves of the rotary encoder in quadrature. The block turns the incremental rotary encoders into 4-bit absolute encoders. | + | |
| - | </ | + | |
| - | {{ :test-and-measurement: | + | |
| - | </ | + | |
| - | + | ||
| - | <WRAP group>< | + | |
| - | While 4-bit binary numbers might be easy to understand for machines, on a user interface (created for humans) numbers should appear in decimal representation. This is why a decoder must be used for the dual 7 segment displays, which displays a number between 1 and 16 (received in binary) in decimal representation. | + | |
| - | </ | + | |
| - | {{ :test-and-measurement: | + | |
| - | </ | + | |
| - | + | ||
| - | <WRAP group>< | + | |
| - | While most FPGA boards have many pins, there are some cases, when " | + | |
| - | </ | + | |
| - | {{ : | + | |
| - | </ | + | |
| </ | </ | ||
| + | <WRAP group> | ||
| === Driver === | === Driver === | ||
| - | <WRAP group> | ||
| After all the blocks were designed and tested, the actual driver can be built. | After all the blocks were designed and tested, the actual driver can be built. | ||
| - | <WRAP group>< | ||
| - | Because both the user interface circuit and the Analog Discovery Pro will be connected to the FPGA board, these connections have to be defined first. To reduce the number of the required wires on the schematic, net labels or on-page connectors can be used. | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | |||
| - | <WRAP group>< | ||
| As almost all conditioning circuits need clock signals, the derived clocks (with the desired frequency) must be obtained from the FPGA clock. Use clock dividers to reduce the frequency. | As almost all conditioning circuits need clock signals, the derived clocks (with the desired frequency) must be obtained from the FPGA clock. Use clock dividers to reduce the frequency. | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | <WRAP group>< | + | The RGB LED control signals are just passed through the FPGA, they are entirely generated by the ADP. The PWM signals coming from the potentiometer conditioning circuits are again buffered, then sent to the multiplexers. |
| - | The RGB LED control signals are just passed through the FPGA, they are entirely generated by the ADP. | + | |
| - | </ | + | |
| - | {{ : | + | |
| - | </ | + | |
| - | <WRAP group>< | + | The play/pause and record buttons are debounced and latched to act like switches. |
| - | The play/pause and record buttons are debounced and latched to act like switches. | + | |
| - | </ | + | |
| - | {{ :test-and-measurement: | + | |
| - | </ | + | |
| - | <WRAP group>< | + | The button selecting |
| - | The PWM signals coming from the potentiometer conditioning circuits are again buffered, then sent to the multiplexers. | + | |
| - | </ | + | |
| - | {{ :test-and-measurement: | + | |
| - | </ | + | |
| - | <WRAP group>< | + | The pulse counter block is used to turn the incremental encoder used as modulation wheel into an absolute encoder. The same is done for the timing wheel, but the latched timing button decides, if the output |
| - | The button selecting the MIDI channel | + | |
| - | </ | + | |
| - | {{ : | + | |
| - | </ | + | |
| - | <WRAP group>< | ||
| - | The pulse counter block is used to turn the incremental encoder used as modulation wheel into an absolute encoder. | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | |||
| - | <WRAP group>< | ||
| - | The same is done for the timing wheel, but the latched timing button decides, if the output is used for the attack time, or for the release time (the distinction is made from software). | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | |||
| - | <WRAP group>< | ||
| - | Octaves are set by two buttons: one buttons shifts the octave up, the other one down. The N counter block is used to set the upper limit of counting to 10 (the block will count from 0 to 10 - 11 positions). | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | |||
| - | <WRAP group>< | ||
| - | The piano keys are debounced and sent to the multiplexers. | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | |||
| - | <WRAP group>< | ||
| The previously created hierarchical blocks are used to display the channel number and the octave index on the 7 segment displays. | The previously created hierarchical blocks are used to display the channel number and the octave index on the 7 segment displays. | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| - | <WRAP group>< | ||
| Finally, the conditioned output signals are multiplexed (4:1) to the digital I/O pins of the ADP. To generate the multiplexer addresses, two digital pins of the ADP are used, the address being generated from software. | Finally, the conditioned output signals are multiplexed (4:1) to the digital I/O pins of the ADP. To generate the multiplexer addresses, two digital pins of the ADP are used, the address being generated from software. | ||
| - | </ | ||
| - | {{ : | ||
| - | </ | ||
| </ | </ | ||
| - | ---- | ||
| - | </ | ||
| - | <-- | ||
| - | |||
| - | Finally connect the conditioned input and output signals into the ADP. Also connect the FPGA board to one USB port of the Analog Discovery Pro, the Wi-Fi dongle to another one and use a third port as a power supply. | ||
| - | |||
| - | --> Connections to the ADP3450 # | ||
| <WRAP group> | <WRAP group> | ||
| - | The next step is to connect | + | === Connecting |
| - | + | In Multisim, define | |
| - | The BNC oscilloscope connectors are connected directly to the drum pad outputs (multiplexed) and the waveform generator channels are used to generate the reference signal for the PWM generator and the -5V DC voltage reference. The USB ports on the ADP are used to power and program the FPGA, connect the Wi-Fi dongle and supply power to the displays and LEDs through a micro USB connector. | + | </ |
| - | + | ||
| - | {{ : | + | |
| </ | </ | ||
| <-- | <-- | ||
| Line 413: | Line 252: | ||
| ---- | ---- | ||
| - | ===== Writing | + | ===== Designing |
| - | ===== Script Overview ===== | + | ==== Hardware Abstraction Layer ==== |
| <WRAP group> | <WRAP group> | ||
| - | The Python script | + | To make the program simpler and easier to understand, a sort of Hardware Abstraction Layer (HAL) is needed: a set of functions, |
| </ | </ | ||
| + | ---- | ||
| + | ==== Configuring the FPGA ==== | ||
| + | <WRAP group> | ||
| + | To configure the FPGA board connected to the ADP3450, with the bit file created with Multisim, the [[https:// | ||
| - | === main.py === | + | Create |
| - | <WRAP group>< | + | <code>djtgcfg init -d ArtyS7</code> |
| - | The main.py file is a wrapper: it imports other modules, initializes the input and output devices and processes, then, in an infinite loop, decodes and sends keypad events. The script is terminated by pressing Ctrl+C, | + | and |
| - | </WRAP><WRAP half column> | + | < |
| - | < | + | |
| - | # initialize input and output processes | + | |
| - | DI.open() | + | |
| - | DO.open() | + | |
| - | # repeat | + | **Note: **// |
| - | while True: | + | |
| - | # send decoded keypad events | + | |
| - | DO.write(DI.read()) | + | |
| - | except KeyboardInterrupt: | + | <WRAP center round tip 80%> |
| - | # exit on Ctrl+C | + | In your script, check for the responses of the commands to determine if the operation was successful, or not. You can try the commands first in a terminal to check possible responses. |
| - | pass | + | |
| - | + | ||
| - | finally: | + | |
| - | # close every opened device/ | + | |
| - | DI.close() | + | |
| - | DO.close() | + | |
| - | </ | + | |
| </ | </ | ||
| + | ---- | ||
| - | === data_input.py | + | ==== MIDI ==== |
| - | <WRAP group>< | + | |
| - | The input module gets the current state of the keypad and compares it to the previous state, then returns only the differences between the two states. This way only the keypad events like pressing and releasing the buttons will be processed. | + | |
| - | </ | + | |
| - | <code python> | + | |
| - | # read the keypad events | + | |
| - | global last_state | + | |
| - | current = KYPD.get_keys() | + | |
| - | buffer = [] # a buffer to store the results | + | |
| - | # if the two states are not equal | + | |
| - | if current != last_state: | + | |
| - | buffer = difference(current, | + | |
| - | + | ||
| - | # if the script has to be debugged | + | |
| - | if param.DEBUG == " | + | |
| - | # print the differences | + | |
| - | print(buffer) | + | |
| - | + | ||
| - | # save the current state of the keypad | + | |
| - | last_state = current | + | |
| - | + | ||
| - | # return the results | + | |
| - | return buffer | + | |
| - | </ | + | |
| - | </ | + | |
| - | + | ||
| - | <WRAP group>< | + | |
| - | The comparison function consists of two loops: the first one takes every element from the first list and compares it with every element from the second one, and the second loop compares every element of the second list to every element of the first one. The results contain tuples of the unique elements and a True, or False constant. If the unique element appears in the first list, the constant is true, otherwise it is false. | + | |
| - | </ | + | |
| - | <code python> | + | |
| - | # return the differences between two lists | + | |
| - | result = [] # store the result | + | |
| - | + | ||
| - | # go through the first list | + | |
| - | for element_a in a: | + | |
| - | unique = True # flag to signal uniqueness | + | |
| - | # compare the current element with every element from the second list | + | |
| - | for element_b in b: | + | |
| - | # if they are equal | + | |
| - | if element_a == element_b: | + | |
| - | unique = False # set the flag to false | + | |
| - | # if the flag is still true | + | |
| - | if unique: | + | |
| - | # append the element and a True constant to the results | + | |
| - | result.append(tuple([element_a, | + | |
| - | + | ||
| - | # go through the second list | + | |
| - | for element_b in b: | + | |
| - | unique = True # flag to signal uniqueness | + | |
| - | # compare the current element with every element from the first list | + | |
| - | for element_a in a: | + | |
| - | # if they are equal | + | |
| - | if element_b == element_a: | + | |
| - | unique = False # set the flag to false | + | |
| - | # if the flag is still true | + | |
| - | if unique: | + | |
| - | # append the element and a False constant to the results | + | |
| - | result.append(tuple([element_b, | + | |
| - | + | ||
| - | # return the results | + | |
| - | return result | + | |
| - | </ | + | |
| - | </ | + | |
| - | + | ||
| - | === data_output.py === | + | |
| - | <WRAP group>< | + | |
| - | The data output module decodes the keypad events according to the parameters set by the user, then sends the decoded message using RTP-MIDI protocol. | + | |
| - | </ | + | |
| - | <code python> | + | |
| - | # send the decoded keypad events | + | |
| - | # go through the buffer | + | |
| - | for element in buffer: | + | |
| - | command = None # a variable to save the event type | + | |
| - | + | ||
| - | # check for event type | + | |
| - | if element[1]: | + | |
| - | # if the key is pressed, send a NOTE_ON command | + | |
| - | command = msg.NOTE_ON | + | |
| - | else: | + | |
| - | # if the key is released, send a NOTE_OFF command | + | |
| - | command = msg.NOTE_OFF | + | |
| - | + | ||
| - | # send the decoded keypress with the command | + | |
| - | MIDI.send(command, | + | |
| - | param.NOTE_keymap.get(element[0]), | + | |
| - | + | ||
| - | # if the script has to be debugged | + | |
| - | if param.DEBUG == " | + | |
| - | # print the current keypad event | + | |
| - | print(element) | + | |
| - | return | + | |
| - | </ | + | |
| - | </ | + | |
| - | + | ||
| - | === parameter.py === | + | |
| - | <WRAP group>< | + | |
| - | The module containing the parameters is the place where the user can modify the behavior of the MIDI controller, by setting a specific command, or note for every button. The structure of the keypad is also set here, along with the list of pins, to which it is connected. | + | |
| - | </ | + | |
| - | <code python># set keypad parameters | + | |
| - | KYPD_rows = [7, 6, 5, 4] # pins connected to keypad rows | + | |
| - | KYPD_columns = [3, 2, 1, 0] # pins connected to keypad columns | + | |
| - | # the layout of the keypad | + | |
| - | KYPD_keymap = [[" | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | </ | + | |
| - | <code python># set event decoding parameters | + | |
| - | NOTE_velocity = 127 # note velocity | + | |
| - | # what do the keys mean | + | |
| - | NOTE_keymap = { | + | |
| - | " | + | |
| - | " | + | |
| - | " | + | |
| - | " | + | |
| - | } | + | |
| - | </ | + | |
| - | </ | + | |
| - | + | ||
| - | === PMOD_KYPD.py === | + | |
| - | <WRAP group>< | + | |
| - | This module contains functions specific to the Pmod KYPD. Initialization is done by initializing the WaveForms SDK, connecting the Analog Discovery Pro, turning on the power supplies and setting a specified list of pins to a starting state. | + | |
| - | + | ||
| - | The state of the keypad is retrieved by sweeping through the row pins, setting one to a HIGH state and the others to a LOW state in every iteration. After setting a specific row to HIGH, all the columns are checked. If the state of a column pin is HIGH, it means, that the button which is in the respective column, in the respective row, is pressed. | + | |
| - | </ | + | |
| - | <code python> | + | |
| - | # get the current state of the Pmod KYPD | + | |
| - | result = [] # buffer to store the results | + | |
| - | + | ||
| - | # go through all the rows | + | |
| - | for index_1 in range(len(rows)): | + | |
| - | # set the state of every row | + | |
| - | for index_2 in range(len(rows)): | + | |
| - | # only one row is HIGH at every moment | + | |
| - | if index_1 == index_2: | + | |
| - | # set the current row HIGH | + | |
| - | WF.write(rows[index_2], | + | |
| - | else: | + | |
| - | # set the current row LOW | + | |
| - | WF.write(rows[index_2], | + | |
| - | + | ||
| - | # check every column | + | |
| - | for index_2 in range(len(columns)): | + | |
| - | # if the current column is HIGH | + | |
| - | if WF.read(columns[index_2]): | + | |
| - | # append the respective key to the results | + | |
| - | result.append(keymap[index_1][index_2]) | + | |
| - | + | ||
| - | # return the results | + | |
| - | return result | + | |
| - | </ | + | |
| - | </ | + | |
| - | + | ||
| - | === WF_SDK.py === | + | |
| - | <WRAP group>< | + | |
| - | To make the project more modular, thus easier to debug, "user friendly" | + | |
| - | </ | + | |
| - | <code python> | + | |
| - | # read a digital pin | + | |
| - | # load internal buffer with current state of the pins | + | |
| - | dwf.FDwfDigitalIOStatus(hdwf) | + | |
| - | dwRead = c_uint32() | + | |
| - | # get the current state of the pins | + | |
| - | dwf.FDwfDigitalIOInputStatus(hdwf, | + | |
| - | # convert the state to a 16 character binary string | + | |
| - | data = list(bin(dwRead.value)[2: | + | |
| - | + | ||
| - | # check the required bit | + | |
| - | if data[15 - pin] != " | + | |
| - | # if it is one, return True (HIGH) | + | |
| - | return True | + | |
| - | else: | + | |
| - | # else return False (LOW) | + | |
| - | return False | + | |
| - | </ | + | |
| - | <code python> | + | |
| - | # write a logic value on a pin | + | |
| - | global output_state | + | |
| - | index = pow(2, pin) # calculate the position of the required pin | + | |
| - | + | ||
| - | # if a HIGH value has to be written | + | |
| - | if state != 0 and state != False: | + | |
| - | # calculate the bitmask | + | |
| - | mask = int(output_state, | + | |
| - | # convert the current state to a list | + | |
| - | temp = list(output_state) | + | |
| - | # edit this list to save the new state | + | |
| - | temp[15 - pin] = " | + | |
| - | # convert back the list to string | + | |
| - | output_state = "" | + | |
| - | + | ||
| - | # if a LOW value has to be written | + | |
| - | else: | + | |
| - | # calculate the bitmask | + | |
| - | mask = int(output_state, | + | |
| - | # convert the current state to a list | + | |
| - | temp = list(output_state) | + | |
| - | # edit this list to save the new state | + | |
| - | temp[15 - pin] = " | + | |
| - | # convert back the list to string | + | |
| - | output_state = "" | + | |
| - | + | ||
| - | # set the bits according to the mask | + | |
| - | dwf.FDwfDigitalIOOutputSet(hdwf, | + | |
| - | return | + | |
| - | </ | + | |
| - | </ | + | |
| - | + | ||
| - | === rtpmidi.py | + | |
| <WRAP group> | <WRAP group> | ||
| - | The module which realizes | + | Start raveloxmidi in the background as a subprocess, to be able to communicate |
| + | < | ||
| + | Don't forget to change | ||
| + | < | ||
| </ | </ | ||
| - | === midi_message.py === | ||
| <WRAP group> | <WRAP group> | ||
| - | This module contains | + | While the background process is running, you can send data sockets containing MIDI data. An extensive |
| - | --> MIDI Message Types # | + | See the drop-downs below for the most important |
| - | + | ||
| - | |<100% 20% 10% 70%>| | + | |
| - | ^ Message Type ^ Code ^ Meaning | + | |
| - | | NOTE_OFF | + | |
| - | | NOTE_ON | + | |
| - | | POLY_KEY_PRES | 0xA0 | Polyphonic Key Pressure (Aftertouch) | + | |
| - | | CTRL_CHG | + | |
| - | | PRG_CHG | + | |
| - | | CH_PRS_AFTT | + | |
| - | | PITCH_BND | + | |
| - | | SYS_EX_START | + | |
| + | --> MIDI Message Types ^ | ||
| + | <WRAP group> | ||
| + | |<100% 10% 10% 50% 10% 10% 10%>| | ||
| + | ^ Message Type ^ Code ^ Meaning | ||
| + | | NOTE_OFF | ||
| + | | NOTE_ON | ||
| + | | POLY_KEY_PRES | 0xA0 | Polyphonic Key Pressure (Aftertouch) | ||
| + | | CTRL_CHG | ||
| + | | PRG_CHG | ||
| + | | CH_PRS_AFTT | ||
| + | | PITCH_BND | ||
| + | | SYS_EX_START | ||
| + | </ | ||
| <-- | <-- | ||
| - | --> MIDI Notes (must be added to the octave) # | + | <WRAP group>< |
| + | --> MIDI Notes ^ | ||
| + | <WRAP group> | ||
| |<100% 50% 50%>| | |<100% 50% 50%>| | ||
| ^ Note ^ Value ^ ^ | ^ Note ^ Value ^ ^ | ||
| Line 692: | Line 319: | ||
| | A# | 10 | | | A# | 10 | | ||
| | B | 11 | | | B | 11 | | ||
| + | </ | ||
| <-- | <-- | ||
| - | + | </ | |
| - | --> MIDI Octaves | + | --> MIDI Octaves |
| + | <WRAP group> | ||
| |<100% 20% 10% 70%>| | |<100% 20% 10% 70%>| | ||
| ^ Notation | ^ Notation | ||
| - | | CN2_O | + | | DBL_CONTRA_O |
| - | | CN1_O | + | | SUB_CONTRA_O |
| - | | SUB_CONTRA_O | + | | CONTRA_O |
| - | | CONTRA_O | + | | GREAT_O |
| - | | GREAT_O | + | | SMALL_O |
| - | | SMALL_O | + | | LINE1_O |
| - | | LINE1_O | + | | LINE2_O |
| - | | LINE2_O | + | | LINE3_O |
| - | | LINE3_O | + | | LINE4_O |
| - | | LINE4_O | + | | LINE5_O |
| - | | LINE5_O | + | | LINE6_O |
| + | </ | ||
| <-- | <-- | ||
| + | </ | ||
| + | </ | ||
| + | <WRAP group> | ||
| + | When you are finished, kill every process with the name " | ||
| </ | </ | ||
| ---- | ---- | ||
| + | ==== MIDI Controller Script ==== | ||
| + | <WRAP group> | ||
| + | In your wrapper script, define the connections between the devices and other parameters like the frequency and amplitude/ | ||
| - | ===== Testing ===== | + | Start the WaveForms instruments needed |
| - | <WRAP group>< | + | |
| - | Copy the Python scripts to a USB flash drive, formatted | + | |
| - | <code>sudo fdisk -l</ | + | The rest of your code should run in an infinite loop, broken only by pressing Ctrl+C. In each iteration set the address pins of the multiplexers in the driver to 0, 1, 2, or 3 (implement a 2 bit counter here), then read the current state of the multiplexer outputs. Decode these outputs according to the current address and compare the results to the previous ones. If there is no change in the state of a user interface element, no new command should be sent, but if there is a change, send the respective MIDI command with raveloxmidi. |
| - | Note the drive letter and the partition number of your drive, as you will have to use these in the following. Mount and open the content of the drive by typing the following commands in the terminal emulator: | + | You might also want to set some visual effects using the RGB LEDs. Use your creativity |
| + | </ | ||
| + | ---- | ||
| - | <code>sudo mount /dev/sda1 /mnt | + | ===== Testing ===== |
| - | cd /mnt</ | + | <WRAP group><WRAP half column> |
| + | Copy the project containing the Python scripts on the Analog Discovery Pro 3450, with the help of a FAT32 formatted USB flash drive, or using [[https://winscp.net/eng/index.php|WinSCP]]. | ||
| - | Run the script by typing | + | Navigate into the project directory, then run the script by typing |
| - | < | + | < |
| </ | </ | ||
| - | {{ : | + | {{ : |
| </ | </ | ||
| Line 744: | Line 379: | ||
| Now you can use your wireless MIDI controller, to play any instrument you like - the limit is your imagination. You can expand the capabilities of your controller by adding extra digital and analog control elements, like sliders, more buttons, switches, etc. | Now you can use your wireless MIDI controller, to play any instrument you like - the limit is your imagination. You can expand the capabilities of your controller by adding extra digital and analog control elements, like sliders, more buttons, switches, etc. | ||
| </ | </ | ||
| - | {{ : | + | {{ : |
| </ | </ | ||
| ---- | ---- | ||
| Line 750: | Line 385: | ||
| ===== Next Steps ===== | ===== Next Steps ===== | ||
| <WRAP group> | <WRAP group> | ||
| - | The script can be modified as needed for your project. | + | The script, the PLD design and the circuit |
| For more information on WaveForms SDK, see its [[software: | For more information on WaveForms SDK, see its [[software: | ||

