MIDImize Final Report
MIDImize Final Report
MIEEIC
Embedded Systems
Professors:
Author: Tiago Gomes
Mário Mesquita PG47499 Ricardo Roriz
Sérgio Pereira
February 6, 2022
Contents
1 Problem Statement 6
2 Analysis Phase 7
2.6.1 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1
3 Design Phase 23
3.2.3 Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2.6 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.3.1 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.3.7 Flowcharts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4 Implementation Phase 49
2
4.1 Changes to the design . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.2.2 Enclosure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.6.1 CSynth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.6.2 CProtectedBuffer . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.6.3 TLed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.6.4 CMidiMize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.6.5 MidiMizeForm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
List of Figures
2 Arturia MicroBrute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
4 Behringer Crave . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3
7 System overview diagram . . . . . . . . . . . . . . . . . . . . . . . . . . 14
10 Use Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
11 State Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
17 Raspberry Pi 4 Model B . . . . . . . . . . . . . . . . . . . . . . . . . . 28
19 Connections Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
20 Task Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
21 Thread Priorities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
28 Class Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
30 GUI Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4
31 Alternative GUI Layout . . . . . . . . . . . . . . . . . . . . . . . . . . 47
32 LED circuit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
38 Buildroot - Qt5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
40 MIDImize GUI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
41 Task division . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
42 Gantt diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5
1 Problem Statement
The music industry is growing exponentially every single day, that’s a fact no one
can deny. With the internet reaching almost every corner of the globe and with musical
instruments becoming cheaper, anyone with ambition can try to become a musician.
One of the most versatile instruments is the synthesizer. They are electronic in-
struments that allow the user to modulate and shape the sound as they want and
are usually played with keyboards that are built-in or connected via MIDI (Musical
Instrument Digital Interface).
MIDI 1.0 exists since 1983 and was the first universal musical instrument digital
interface. With the introduction of MIDI 2.0, the standard takes the specification
further, adding a lot of new features and retaining backward compatibility with MIDI
1.0 gear and software. Nearly four decades on, it’s clear that MIDI still remains viable
and relevant.
There are lots of options available to play the synthesizer: a MIDI keyboard plugged
into a computer using a software synthesizer, a MIDI keyboard plugged into a synthe-
sizer module, etc. In order not to be dependent on a computer, a synthesizer module
or a synthesizer with a built-in keyboard can be used.
But, as with everything, there are some drawbacks. Synthesizers are not cheap.
Most synthesizers with built-in keyboards cost more than 200e and synthesizer modules
can be cheaper but to take full advantage of them a keyboard must be plugged in.
Learning how to use synthesizers in order to produce the desired sounds is difficult, and
can quickly become an overwhelming task. That’s where MIDImize comes in.
MIDImize can also be played without any controller. With Solo mode, the user can
play the instrument without the need to plug in a MIDI controller.
6
2 Analysis Phase
The music instrument market is packed with synthesizers, ranging in prices any-
where from 60 euros to several thousands. Some are more complete than others, having
a wider range of controls, sound synthesis and processing capabilities and many other
features.
MIDImize has the music enthusiast in mind, offering a cheap alternative to some of
the more expensive synthesizers already on the market. The second hand market is full
of old MIDI compatible keyboards from the 90s that can be purchased for a very low
price and plugged into MIDImize.
With MIDImize, anyone can enjoy the power and versatility of the synthesizer with-
out breaking the bank.
OP-1
7
Features:
• Built in LCD;
• Built-in Sampler;
• Included Sequencer;
MicroBrute
Features:
• Built in Oscillator;
• Included Filters;
• Modulation Matrix;
• Sequencer;
• Syncable LFO.
8
Synthesizers modules
Rocket
Features:
• Included Arpeggiator;
• 8 Oscillators;
9
Crave
Features:
• Included Arpeggiator;
• Built-in Sequencer;
• Oscillator;
10
2.2 Problem statement analysis
One of the features that characterizes MIDImize, and is not seen in other devices,
is the ability to help the user learn about synthesizers and achieve the desired sounds.
The device screen will provide information about every element in the interface (knobs,
buttons, sliders, etc), tips and tricks.
The majority of synthesizers on the market have interfaces with fixed physical but-
tons and knobs that perform specific functions. This kind of interface implementation
limits the device functionality and features. Besides that, interfaces can get cluttered
with elements making it difficult to the not so advanced user to learn how to achieve
the sound they are looking for.
11
MIDImize will use an LCD touchscreen to interface with the user. A touchscreen is
ideal to display any information to the user and it’s very versatile. A keyboard interface
can be implemented on the display for the users who don’t own one. The layout of the
interface elements can be changed on the fly and more functions can be easily added
with a software update.
In MIDI mode, any MIDI compatible controller can be plugged in and used to play.
In Solo mode, no controller is required to play the instrument. When Solo mode is
activated, MIDImize outputs the sound in a continuous fashion.
The keywords when talking about Open-Source projects are: Collaboration and
Community.
MIDImize wants to help the user playing the synthesizer without having to watch
tutorials or read complicated articles. The best way to know how to help users learning
about synthesizers is by receiving feedback from them. Building a community around
MIDImize will rapidly increase its user-base, and have more people using, testing,
promoting, providing feedback and ultimately affecting the software and the project
itself.
According to the Linus’s law, given enough eyeballs, all bugs are shallow. In an
Open-Source project bugs are usually fixed in no time, so the project ensures stability
and is more reliable.
With so little time to develop the project and many other constraints, it’s difficult
to produce a device that attracts consumers, is well priced and compliant with industry
standards. Open-Source is the way to go.
12
2.3 System overview
To better understand how MIDImize will work, the entities that interact with the
system were identified and the relationships between them simplified in this diagram.
MIDImize will will have two main modes of operation: Solo Mode and MIDI Mode.
• Solo Mode - While in Solo Mode the device must be able to function like a
standalone instrument, outputting sound continuously;
13
• MIDI Mode - In MIDI Mode, a MIDI controller must be plugged in, like a drum
pad or a keyboard, for example.
The LCD display will let the user quickly change the operation mode and let him
know in what mode he’s playing. The user can also use the LCD display to switch
between different oscillators/sounds and change its parameters. The sound then outputs
through the speakers connected to MIDImize and the LED’s will blink according to the
frequency being played by the oscillators.
In order to design this system, there is the need to specify all the constraints and
requirements.
14
• Low cost;
• Open-Source;
• Small footprint;
• User-friendly interface;
• Limited budget;
• Team of 2 people;
• Global electronics component shortage may delay the assembly of the device;
15
2.4 System architecture
In this section a clear difference will be established between the several parts that
constitute the system, both hardware and software.
The raspberry will receive a MIDI output from any USB MIDI compatible device.
The touchscreen connects to the raspberry pi via HDMI for the image output and USB
for the touch input. The speakers connect directly to the board’s audio jack and the
LED’s will be connected to the GPIO (for power and signals).
16
• LCD touchscreen - must have a touchscreen between 7-10 inches in order to be
user-friendly. It must support multi-touch and be able to connect with the Rasp-
berry Pi via HDMI;
• Speakers - The speakers must be active speakers because no amplifier will be used.
• LED’s - The LED’s will be soldered in a PCB and connected to the raspberry
pi’s GPIO.
The system software architecture can be divided into two parts, as seen in the figure
9.
The device drivers will provide an interface with the hardware. The core functional-
ity of the system is present on the upper software layers. In this case they are referred
as threads.
Time is a fundamental parameter in music. Most music systems have soft deadlines,
so data should be computed even if late. With that in mind MIDImize is set to be a
soft Real Time System.
17
2.6 Local System
2.6.1 Events
The system events were identified and presented in the following table, as well as its
actors and the synchronization type. When the system is powered on, it is initialized
and an operation mode is automatically selected. When the user interacts with any
element in the GUI, it’s value will be updated in the synth module. If the system is
running in MIDI mode and a note is played in the MIDI controller, MIDImize will
process the note, generate the sound and output it. To note that all of the system
events are asynchronous.
Two main actors interact with MIDImize, the User and the MIDI controller. The
user interacts with the system by selecting which mode he wants to use, as well as what
parameters he would like to change to generate the sound. After selecting the mode
to play the user can adjust several elements present in the GUI, like knobs, sliders,
buttons, etc. Each of the elements is linked to a specific parameter like Oscillator
Type, Attack, Sustain, Release, LFO Rate, etc Once these parameters are changed the
synth is generated and sent to the DAC module. The user can also adjust the main
volume.
The MIDI Controller only has one possible interaction: to send a note using the
MIDI protocol. After that note message is sent, it will be processed by the MIDI
Module.
To note that the interaction with the touchscreen will be a two way interaction:
The information will be displayed on the screen and updated in real time whenever the
18
user presses any of the buttons present on the GUI.
In the figure 11 the basic behaviour of the system can be observed. The system
starts off in idle mode, but is not intended to stay in that mode for long. After that it
can go one of two ways: MIDI Mode or Solo Mode.
If the system detects that a MIDI controller is plugged in, it automatically switches
19
to MIDI Mode and runs in that mode until the controller is unplugged. If there is no
MIDI Device connected, the system will start off in Solo Mode and run in that mode
until a MIDI controller is plugged in. Whenever it detects that a MIDI controller has
been plugged in, it will switch to MIDI mode.
20
2.6.4 Sequence Diagrams
21
Figure 13: Sequence Diagram: Playing MIDI note
In the following table the individual cost of the components have been layed out.
• Although it would be cheaper to buy online, the LCD price is from a local store
given the fact that most online retailers don’t guaranty that the required item
would arrive on time;
• The price indicated for the Raspberry Pi is the normal retail price with an included
5V power supply;
22
3 Design Phase
Throughout this project various subjects will be approached and, as such, they
need to be explained first. The following paragraphs consist of an explanation about
the MIDI Protocol and Additive Synthesizers.
The MIDI Protocol appeared from the need to simplify the communication between
musical instruments, computers and other related devices.
The main principle of MIDI is that any device that uses it must be able to connect
to any other MIDI device ever made, despite it’s age, make or model, meaning that
a device created today that has a MIDI port is still able of interacting with a device
manufactured over 30 years ago.
But how does the MIDI Protocol work? It operates on the principle that the logical
0 is the current-on pulse and the logical 1 is the current-off pulse. After settling that, a
MIDI byte, or message, is created. At the start of every message resides a logical 0 that
acts as a start bit and, at the end, there is always a 1, signaling that the message has
terminated. In the middle there are 8 bits left for data. If by any chance the start bit
of the next byte arrives before the stop bit of the current message, a ”framing error” is
declared and the data will note be received correctly.
After understanding how a MIDI byte is constituted, a further analysis can be made.
When a key is pressed on a MIDI compatible keyboard, a message consisting of three
bytes is generated. The first byte is known as the Command byte or Status byte. In
the case that a note is being played, this byte is called ”Note On”. Both the second
and third bytes are considered Data bytes, containing the parameters for what to do in
regard to the Command byte. Following is a summarized explanation of the 3 bytes:
23
• The first byte describes the kind of action for instance, that a key has been pressed
(Note on);
• The second byte is the number of the key that’s been pressed, that is, which note
has been played;
• The third byte is the velocity the key has been pressed with, which can help
determine the volume of a note
If the first bit of a MIDI message is set to ”1”, the message will be a Command
byte. On the other hand, if it’s set to ”0”, the message will be a Data byte. When
translating this message to hexadecimal notation, all Command bytes start with 8 or
higher and all Data bytes start with 7 or less.
For a MIDI message to be interpreted properly it must consist of the correct number
of bytes and it must not be interrupted, except in some special cases. If an insufficient
number of bytes follows a Command byte before the next Command byte is received,
the receiving device will assume that the received information was an error.
A special case occurs when the number of Data bytes following the Command byte
is too high. This invokes a condition of the MIDI data stream called ”Running Status”.
It is invoked if a Command byte is received and is processed just as if the Command
byte has been repeated. When this condition is active, a note can be turned off with
just a number and velocity (2 bytes), instead of a full note-off command (3 bytes).
24
3.1.2 Additive Sound Synthesis
The basic way a synth works is by firstly creating a simple sound. After that is in
place, the general outline of the synth can be built. When that’s done, the finishing
touches can be put in place.
Almost every synthesizer possesses these 4 parts:
• Oscillator;
• Filters;
• LFO;
• ADSR Envelope;
Additive synthesis is considered the oldest form of sound synthesis, dating back to
its applications in pipe organs where it was used to rout air through various pipes in a
way that would create different timbres. Later, in the beginning of the 19th century,
Jean Fourier made the theory that complex sounds can be broken up into several simple
component sounds.
On the present day, they are mainly used to very accurately model almost any musical
instrument, given enough computational resources. This is one of the reasons why ad-
ditive synthesis is mainly implemented digitally and not in analog circuitry.
These synthesizers are based on the Fast Fourier Transform theory that says that
any audio signal can be broken down to a series of sine waves (usually referred to as
”partials”) and that any sound can be recreated with the right combo of sine ”build-
ing blocks”. This essentially means that by incrementally adding simple wave-forms
together, the desired results can be achieved. Although in theory any wave-forms can
be used, the use of sine waves make the process highly predictable and controllable.
When it comes to creating the actual sound, the first partial, also known as the “fun-
damental frequency”, is considered the most important to the overall sound. This fun-
damental frequency determines the overall loudness and pitch of the composite sound.
Although the other partials also have an effect on these parameters of the final sound,
its most apparent effect is felt on the overall timbre of the resulting sound
This process of adding ”partial” waves together to achieve more complex wave-forms
is the basics behind additive synthesis.
25
Figure 15: Basics of additive synthesis
After these basic waves are defined, some other interesting wave-forms can be made
by introducing in-harmonic frequencies at various amplitudes and phases. In the end,
some noise can also be added in order to create a sound with more punch. But when
adding noise, the user must be careful because of the fact that a large number of sound
sources have been previously added together. Since each of these sound sources has
some noise, adding them together will necessarily increase the noise level in the final
sound.
So, if what has been said before is true and any complex sound can be built with
basic sine waves, logic dictates that given enough processing power, any sound can be
analyzed and broken down into a collection of sine waves with varying attributes. Once
a sound is broken down into a table of data with numbers representing amplitude, fre-
quency, phase and time, those numbers can be manipulated at will for a multitude of
applications such as changing pitch without changing duration and vice versa or visu-
alizing sounds in a spectral display.
For the most basic example of additive sound synthesis, if to a basic sine wave that
has a frequency of 100HZ and a amplitude of 1, three other sine waves are added at
the harmonics (300Hz, 500Hz and 700Hz in this case) with the amplitude modulated
to 1/3 in the 3rd harmonic, to 1/5 in the 5th harmonic and 1/7 in the last harmonic,
a basic square wave is created.
26
Figure 16: Example of additive synthesis
27
3.2 Hardware Specification
• Micro-SD card slot for loading operating system and data storage;
28
3.2.2 7” LCD Screen
To enable the interaction between the user and the GUI, a 7” LCD with a capacitive
touchscreen was used.
Here are some of the most important features:
3.2.3 Architecture
29
video interface and the USB port for the touch interface. The touch interface doesn’t
require any special drivers as it is picked up as a pointing device.
To note that the LCD screen can be powered by the USB and HDMI ports present
on the Raspberry Pi.
The 3.5mm audio jack can also be used to connect any external amplified speaker.
Finally and as previously mentioned, MIDImize has two operating modes, the Solo
and MIDI mode. In order to be able to play in MIDI mode a MIDI controller will have
to be plugged in. For that the user will plug his controller to one of the USB ports
available.
Before the implementation phase starts some tests must be carried out to determine
if the hardware specifications match the expectations and if they can live up to the tasks
at hand. Given the fact that the GUI is the most important part of the user-machine
interaction, that’s where the majority of the tests will take place.
30
First of all it is important to ensure that the display is displaying the video output
from the raspberry and that it’s seen as an input device (mouse). After that, to test
the functionality of the display, a simple QT app can be made with some buttons and
sliders.
3.2.6 Tools
To be able to manufacture the enclosure in a way that matches the exact dimensions
necessary, meaning that it won’t be to small or to big, not causing any wasted space
and added weight, a 3D printer was used.
31
3.3 Software Specification
3.3.1 Tools
• ALSA;
• SuperCollider;
• Overtone;
• Qt libraries;
• Pthreads;
Alsa, or The Advanced Linux Sound Architecture provides an API to interface with
the raspberry pi audio.
SuperCollider is a platform for audio synthesis with some components that this
project will benefit from like a real-time audio server with many unit generators, it’s
own programming language to make it easier to achieve the desired goal and a built-in
editor to use the aforementioned programming language.
32
Pthreads, or POSIX threads defines an API to create and manipulate threads.
The MIDI input is one of the main features of MIDImize. In order to interface with
the MIDI keyboard, a MIDI device driver will be implemented.
This device driver will read the messages sent from the MIDI keyboard and provide a
set of functions to be used in the main application.
The MIDI driver will be a character device driver. A character device driver com-
municates with the user space program by sending and receiving single characters.
• read();
• open();
• init();
• exit():
In this case, a thread is a task to be executed by the main process. Two protected
buffers and a protected struct provides the synchronization between tasks.
All the threads to be implemented and the synchronization between them are dis-
played in the Tasks Diagram.
33
Figure 20: Task Diagram
• Protected Buffer
This protected buffer provides one mutex for data protection and two condition
variables for thread synchronization. This buffer follows a producer-consumer
model.
Music systems usually have soft deadlines in the sense that data should be computed
even if it is late. It’s very difficult to predict the sound generation overhead so the most
important task to run is the sound generation. Therefore, the sound generation threads
have higher priority.
34
3.3.7 Flowcharts
• CProtectedBufferPush
35
Figure 22: Protected Buffer Push flowchart
36
• CProtectedBufferPop
37
Figure 23: Protected Buffer Pop flowchart
38
• MIDIThread
This flowchart describes how the MIDIThread works. The MIDIThread is the
producer of MIDI messages.
Firstly, it uses the MIDI device driver to check if a MIDI keyboard is plugged in.
If that’s the case, this thread will read a MIDI message from the MIDI device
driver and push it to the protected buffer.
39
• MessageProcessingThread
40
• SoundGenerationThread
41
• Audio Output Thread
42
3.3.8 Class Diagram
The class diagram shows all the system classes and their methods and attributes.
MIDImize has one main class that represents the system called CMidimize. CMidimize
must only be instantiated once (singleton class) and all the other classes must not be in-
stantiated if CMidimize doesn’t exist. This class has one object of each class (threads),
a Synth object (protected struct with the synth data) and two CProtectedBuffer objects
(one to store MIDIMessage objects and other to store the wave to be played).
Synth class has no attributes, so it functions like a data structure. It has a mutex
to protect it’s data when accessed by system threads, a solo-mode boolean to store the
system mode, the volume and one object for each synthesizer parameter (Oscillator,
ADSR, Filter and LFO).
The tProtectedBuffer class is responsible for the data communication and synchro-
nization between threads. It’s a template class, so it abstracts the data types it accepts.
This class provides the methods to pop and push from the buffer.
The tMidiThread class is responsible for acquiring the MIDI message from the MIDI
device driver. It provides the methods to create, join and exit the thread as well as
the method acquire-message() that grabs the MIDI message. It is composed by a
MidiMessage that is a struct to encapsulate the MIDI message data.
The tAudioOutputThread is responsible for grabbing wave points from the protected
buffer and output the sound using the audio device drivers. It provides the methods
to create, join and exit the thread as well as the methods to interact with the audio
drivers.
43
Figure 28: Class Diagram
The graphical interface will allow the user to achieve the desired sound via the
manipulation of the many present knobs and sliders. Every element of the GUI pos-
sesses a box with a question mark that will briefly explain what that part accomplishes.
44
Figure 29: Example of a question box
When it comes to the GUI, two different designs are being considered. While one of
them is more complex by presenting one extra functionality, it will only be considered
if there is enough time by the end of the project. With this first explanation we can
proceed to talk about the actual elements present and, in the end, the changes to the
second design will be presented.
Starting from the left, that’s where the oscillator menu and the Solo/MIDI switch
resides. The oscillator menu allows the user to pick between four possible waves (sine,
saw tooth, triangular and square) and adjust it’s frequency and range via circular knobs.
The Solo/MIDI switch does exactly what the name implies, it will allow the user to
switch between the two modes of operation.
The middle column is where the ADSR and filter menus reside. In the ADSR the
user will be able to control the level of the four elements (attack, delay, sustain and
release) and, in the filter menu, the cutoff frequency can be selected.
In the last column of the GUI sits the Low frequency oscillator menu and the main
volume knob. In the LFO menu the frequency can be chosen, as well as it’s phase and,
in the main volume section, the sound output from the system can be controlled.
45
Figure 30: GUI Layout
Now for the changes to the alternative design. Firstly the SOLO/MIDI switch is
shifted to the top right corner and the filter menu is pushed up to sit in level with
the oscillator and LFO modules. In the extra space created resides the keyboard/wave
menu. In this menu, while in keyboard mode, a keyboard will be displayed, allowing
the user to play the desired notes. On the other hand, when in wave mode, the sound
wave that is being produced will be shown.
46
Figure 31: Alternative GUI Layout
As previously seen in the hardware specifications, here in the software some tests
also need to be carried out to make sure everything that was planned is working as
intended.
Given that the product has two different modes of operation, different tests need to
be carried out, some for MIDI mode, some for Solo mode and some that are transverse
to both of them.
47
Solo Mode:
MIDI Mode:
Mode independent:
48
4 Implementation Phase
After the start of the implementation phase, the team was reduced to one element.
The implementation continued based on the work done at the design phase. However,
with the deadline for the project approaching, and with such limited time, it became
evident that it would be impossible to fulfill the requirements set in the analysis phase.
The decision to change the design was then made. In the next section the design changes
are described as well as the thought process behind them. The following sections
will focus on the information about the implementation of MIDImize’s hardware and
software.
The main objective of the design changes was to be able to fulfill the requirements
set in the analysis phase and have a fully functional product before the deadline. The
solution to accomplish that was to use SoundFont files and a third-party library to
handle the MIDI events and the sound generation.
SoundFont is a file format and technology that plays MIDI files using sample based
synthesis. SoundFont files contain samples in PCM (Pulse-code modulation) format
that are then mapped to the corresponding MIDI notes. MIDImize runs on a Raspberry
Pi with ALSA drivers, that also use PCM for sound processing, so SoundFont files are
fully compatible. To handle MIDI events and manage SoundFont files, MIDImize will
use FluidSynth. FluidSynth is a third party library that provides an API to read
SoundFont files, handle MIDI events and output digital audio to the sound card.
An effort was made to keep as much as possible from the previous design. In
that regard, only the MIDI handling and sound generation threads were shaved off.
The CProtectedBuffer class was designed to be abstracted from all data types and to
provide synchronization and communication between any thread, so no changes were
made to it. The rest of the software design is basically the same with a cSynth class
that stores the running state of the synthesizer and the main class CMIDImize that
initializes the system.
To add value and some visual ”pop” to MIDImize, 3 LED’s will be added to the
enclosure, one for each oscillator and one to indicate the power status. A TLed thread
and 3 device drivers were also designed to handle all of the LED’s functionality.
49
4.2 Hardware implementation
To drive the LED’s a simple circuit using a transistor was used. The resistor R1
limits the current flowing throw the LED and resistor R2 limits the current flowing from
the raspberry pi GPIO pin while still allowing for the full opening of the transistor.
The circuit presented in the figure 32 was used for all three LED’s. It was tested
using a breadboard, but for the final version will be soldered into a PCB and placed
inside the enclosure.
50
4.2.2 Enclosure
The enclosure was designed to hide all the hardware and cables and to provide a
mounting bracket for the LCD touchscreen. It features an opening in the back for the
cables to route through and a sliding back door. In the front there are three holes for
the LED’s.
At the time this report is being written the enclosure isn’t finished. The printing
job is currently being made by a Prusa MK3 and will take 1 day and 21 hours to finish.
51
Figure 34: Enclosure without the screen and the sliding back door
52
4.3 Buildroot configuration
In order to have a running environment for MIDImize, buildroot was used. Buildroot
allows the creation of linux images for embedded devices, enabling cross compiling. The
default configuration for the Raspberry 4 was used, then some packages were added.
For the sound generation MIDImize uses ALSA drivers and fluidsynth, so those
packages were added to the buildroot configuration.
The GUI is built with Qt, so the Qt5 package was added. The default platform to
53
run Qt apps was set to ”linuxfb” because after several tries it wasn’t possible to run
them using hardware acceleration.
54
4.4 Device drivers
To allow the MIDImize appplication to interact with the hardware devices three
device drivers were created, one for each LED. These device drivers provide an interface
that enables user space applications to communicate with the kernel. The device drivers
created for MIDImize are character device drivers and each LED has it’s own driver.
When inserted they will create a device under the linux device tree. These drivers will
change the logic state of the associated GPIO pin when an IOCTL function is called in
the user space application. IOCTL, or Input and Output Control, is used by user space
applications to talk with device drivers, and can handle specific operations of a device
for which the kernel does not have a default system call. In this drivers, two IOCTL
functions were implemented: blink and power. The blink function will make the LED
blink and the power function turns the LED on or off. To make the LED blink, a kernel
timer was used. A kernel timer works by incrementing a kernel-internal value, called
jiffies, every time there is a timer interrupt. After setting up and starting the timer,
when there’s a timeout the timer callback is called, and the LED is turned on or off,
making it blink.
1 v o i d t i m e r c a l l b a c k ( s t r u c t t i m e r l i s t ∗ data )
2 {
3 i f ( g p i o g e t v a l u e (GPIO OSC1 LED) )
4 {
5 g p i o s e t v a l u e (GPIO OSC1 LED , 0 ) ;
6 }
7 e l s e i f ( ! g p i o g e t v a l u e (GPIO OSC1 LED) )
8 {
9 g p i o s e t v a l u e (GPIO OSC1 LED , 1 ) ;
10 }
11 mod timer(& o s c 1 t i m e r , j i f f i e s + m s e c s t o j i f f i e s ( f r e q ) ) ; // t i m e r
will expire after freq miliseconds
12 }
13
14 s t a t i c l o n g o s c 1 l e d i o c t l ( s t r u c t f i l e ∗ f i l e , u n s i g n e d i n t cmd , u n s i g n e d
long arg )
15 {
16 s w i t c h (cmd)
17 {
18 /∗ BLINK IOCTL FUNCTION ∗/
19 c a s e BLK:
20 i f ( c o p y f r o m u s e r (& f r e q , ( i n t 3 2 t ∗ ) arg , s i z e o f ( f r e q ) ) )
21 {
22 p r e r r ( ”ERROR w r i t i n g data \n” ) ;
23 }
24 blink = 1;
25 mod timer(& o s c 1 t i m e r , j i f f i e s + m s e c s t o j i f f i e s ( f r e q ) ) ; //
timer w i l l expire a f t e r f r e q miliseconds
26 p r i n f o ( ” B l i n k i n g l e d − %d ms\n” , f r e q ) ;
27 break ;
55
28
29 /∗ POWER IOCTL FUNCTION ∗/
30 c a s e PWR:
31 i f ( c o p y f r o m u s e r (& c t r l , ( i n t 3 2 t ∗ ) arg , s i z e o f ( c t r l ) ) )
32 {
33 p r e r r ( ”ERROR w r i t i n g data \n” ) ;
34 }
35 i f ( c t r l == 1 )
36 {
37 i f ( blink )
38 {
39 d e l t i m e r (& o s c 1 t i m e r ) ;
40 b l i n k=f a l s e ;
41 }
42 g p i o s e t v a l u e (GPIO OSC1 LED , 1 ) ;
43 p r i n f o ( ” o s c 1 l e d ON\n” ) ;
44 }
45 e l s e i f ( c t r l == f a l s e )
46 {
47 i f ( blink )
48 {
49 d e l t i m e r (& o s c 1 t i m e r ) ;
50 b l i n k=f a l s e ;
51 }
52 g p i o s e t v a l u e (GPIO OSC1 LED , 0 ) ;
53 p r i n f o ( ” o s c 1 l e d OFF\n” ) ;
54 }
55 else
56 {
57 p r i n f o ( ”Unknown PWR command\n” ) ;
58 }
59 break ;
60
61 default :
62 p r i n f o ( ” D e f a u l t i o c t l command\n” ) ;
63 break ;
64 }
65 return 0;
66 }
Code 1: LED device driver timer callback and IOCTL functions
Each driver also implements the init function to insert the driver and the exit
function to deallocate it from the kernel, and file operations like open, read, write,
release and IOCTL.
1 static int i n i t o s c 1 l e d i n i t ( void )
2 {
3 /∗ A l l o c a t e major and minor numbers ∗/
4 i f ( ( a l l o c c h r d e v r e g i o n (&dev , 0 , 1 , ” o s c 1 l e d ” ) ) <0)
5 {
6 p r e r r ( ” Unable t o a l l o c a t e major number f o r d e v i c e : o s c 1 l e d \n” ) ;
56
7 u n r e g i s t e r c h r d e v r e g i o n ( dev , 1 ) ;
8
9 r e t u r n −1;
10 }
11 p r i n f o ( ” De vi c e : o s c 1 l e d Major = %d Minor = %d \n” , MAJOR( dev ) ,
MINOR( dev ) ) ;
12
13 /∗ Add d e v i c e t o c h a r a c t e r d e v i c e subsystem ∗/
14 c d e v i n i t (& o s c 1 l e d c d e v ,& f o p s ) ;
15
16 i f ( ( cdev add (& o s c 1 l e d c d e v , dev , 1 ) ) < 0 )
17 {
18 p r e r r ( ” Unable t o add o s c 1 l e d \n” ) ;
19 c d e v d e l (& o s c 1 l e d c d e v ) ;
20 u n r e g i s t e r c h r d e v r e g i o n ( dev , 1 ) ;
21
22 r e t u r n −1;
23 }
24
25 /∗ C r e a t e d e v i c e c l a s s s t r u c t ∗/
26 i f ( ( d e v c l a s s = c l a s s c r e a t e (THIS MODULE, ” o s c 1 l e d ” ) ) == NULL)
27 {
28 p r e r r ( ” Unable t o c r e a t e o s c 1 l e d d e v i c e c l a s s s t r u c t \n” ) ;
29 class destroy ( dev class ) ;
30 c d e v d e l (& o s c 1 l e d c d e v ) ;
31 u n r e g i s t e r c h r d e v r e g i o n ( dev , 1 ) ;
32
33 r e t u r n −1;
34 }
35
36 /∗ C r e a t e d e v i c e ∗/
37 i f ( ( d e v i c e c r e a t e ( d e v c l a s s , NULL, dev , NULL, ” o s c 1 l e d ” ) ) == NULL)
38 {
39 p r e r r ( ” Unable t o c r e a t e o s c 1 l e d d e v i c e \n” ) ;
40 d e v i c e d e s t r o y ( d e v c l a s s , dev ) ;
41 class destroy ( dev class ) ;
42 c d e v d e l (& o s c 1 l e d c d e v ) ;
43 u n r e g i s t e r c h r d e v r e g i o n ( dev , 1 ) ;
44
45 r e t u r n −1;
46 }
47
48 /∗ Check i f GPIO i s v a l i d ∗/
49 i f ( g p i o i s v a l i d (GPIO OSC1 LED) == f a l s e )
50 {
51 p r e r r ( ”GPIO : %d i s not v a l i d \n” , GPIO OSC1 LED) ;
52 d e v i c e d e s t r o y ( d e v c l a s s , dev ) ;
53 class destroy ( dev class ) ;
54 c d e v d e l (& o s c 1 l e d c d e v ) ;
55 u n r e g i s t e r c h r d e v r e g i o n ( dev , 1 ) ;
56
57 r e t u r n −1;
57
58 }
59
60 /∗ Request GPIO ∗/
61 i f ( g p i o r e q u e s t (GPIO OSC1 LED , ”GPIO 24” ) < 0 )
62 {
63 p r e r r ( ”ERROR r e q u e s t i n g GPIO %d\n” , GPIO OSC1 LED) ;
64 g p i o f r e e (GPIO OSC1 LED) ;
65 d e v i c e d e s t r o y ( d e v c l a s s , dev ) ;
66 class destroy ( dev class ) ;
67 c d e v d e l (& o s c 1 l e d c d e v ) ;
68 u n r e g i s t e r c h r d e v r e g i o n ( dev , 1 ) ;
69
70 r e t u r n −1;
71 }
72
73 t i m e r s e t u p (& o s c 1 t i m e r , t i m e r c a l l b a c k , 0 ) ; // k e r n e l t i m e r s e t u p
74 o s c 1 t i m e r . e x p i r e s = j i f f i e s + HZ ; // k e r n e l t i m e r t i m e o u t // HZ =
100 hz f r e q u e n c y o f t h e system t i m e r ( t i c k r a t e )
75
76
77 g p i o d i r e c t i o n o u t p u t (GPIO OSC1 LED , 0 ) ; // S e t s GPIO 24 a s an output
78 g p i o e x p o r t (GPIO OSC1 LED , f a l s e ) ; // d i r e c t i o n m a y c h a n g e = f a l s e
−> u s e r s p a c e i s not a l l o w e d t o change GPIO d i r e c t i o n
79
80 p r i n f o ( ” De vi c e o s c 1 l e d c r e a t e d . K e r n e l module i n s e r t e d . \n” ) ;
81
82 return 0;
83 }
Code 2: LED device driver init function
The graphical user interface of the system is very important because it’s the only
way the user has to interact with MIDImize. The GUI can be seen in the figure 40
below.
58
Figure 40: MIDImize GUI
The GUI is composed of five main sections. The bottom section is a keyboard that
can be used in Solo Mode. In the left side there is a master volume knob and the Solo
and Midi mode selector and the right side is a pitch bend slider. The two main sections
are the oscillators. There are two individual oscillators that can be turned on or off
with individual reverb and chorus controls. For each oscillator the user can select one
of three different sounds.
• CSynth
• CProtectedBuffer
• CMidiMize
• TLed
• MidiMizeForm
59
4.6.1 CSynth
The CSynth class holds all the data for the oscillator and provides an interface for
controlling all its parameters.
1 c l a s s cSynth
2 {
3 private :
4 f l u i d a u d i o d r i v e r t ∗ FsAudioDriver ;
5 f l u i d m i d i d r i v e r t ∗ FsMidiDriver ;
6 f l u i d m i d i r o u t e r t ∗ FsMidiRouter ;
7 f l u i d s e t t i n g s t ∗ FsSettings ;
8 f l u i d s y n t h t ∗ FsSynth ;
9 s o u n d f o n t t sFonts [ 3 ] ;
10
11 f l o a t gain ;
12 bool midi on ;
13
14 public :
15 cSynth ( ) ;
16 ˜ cSynth ( ) ;
17
18 v o i d noteOn ( i n t chan , i n t key , i n t v e l ) ;
19 v o i d n o t e O f f ( i n t chan , i n t key ) ;
20 void setReverb ( ) ;
21 void setChorus ( ) ;
22 void s e t O s c i l l a t o r ( ) ;
23 void setGain ( f l o a t gain ) ;
24 void setPitch ( ) ;
25 void i n i t v a l u e s ( ) ;
26 void d e l e t e s y n t h ( ) ;
27 void i n i t s y n t h ( ) ;
28 void i n i t m i d i ( ) ;
29 void stop midi ( ) ;
30
31 c h o r u s s e t t i n g s t chorus ;
32 r e v e r b s e t t i n g s t reverb ;
33 oscillator t oscillator ;
34 i n t pitchBend ;
35 int current note ;
36 b o o l synthOn ;
37 } ;
Code 3: CSynth class interface
1 enum o s c i l l a t o r t
2 {
3 COSMIC,
4 AMBIANCE,
60
5 ANALOG,
6 };
7
8 struct sound font t
9 {
10 s t r i n g name ;
11 const char ∗ fileName ;
12 int id ;
13 };
14
15 struct chorus settings t
16 {
17
18 float active ;
19 f l o a t speed ; // 0.0 − 5.0
20 i n t nr ; // 0 − 99
21 float lvl ; // 0.0 − 10.0
22 f l o a t depth ; // 0.0 − 21.0
23 f l o a t waveType ; // FLUID CHORUS MOD SINE, FLUID CHORUS MOD TRIANGLE
24 };
25
26 struct reverb settings t
27 {
28 bool active ;
29 f l o a t width ; // 0 . 0 − 100.0
30 f l o a t room ; // 0 . 0 − 1.0
31 float lvl ; // 0 . 0 − 1.0
32 f l o a t damp ; // 0 . 0 − 1.0
33 };
Code 4: CSynth data structures
A CSynth object is the equivalent of an oscillator for MIDImize. To create a Synth, first
it needs to be initialized with the initialization settings, then a FluidSynth instance is
created and the SoundFonts are loaded. Lastly it is created a FluidSynth audio driver
to connect the FluidSynth instance to the audio driver and the data structures are
initialized with the default values.
1 cSynth : : cSynth ( )
2 {
3 /∗ C r e a t e s e t t i n g s o b j e c t and p a s s i n i t v a l u e s ∗/
4 t h i s −>F s S e t t i n g s = n e w f l u i d s e t t i n g s ( ) ;
5 f l u i d s e t t i n g s s e t n u m ( F s S e t t i n g s , ” synth . sample−r a t e ” , FS SAMPLE RATE
);
6 f l u i d s e t t i n g s s e t i n t ( F s S e t t i n g s , ” synth . cpu−c o r e s ” , FS CPU CORES) ;
7 f l u i d s e t t i n g s s e t s t r ( F s S e t t i n g s , ” a u d i o . d r i v e r ” , FS AUDIO DRIVER) ;
8 f l u i d s e t t i n g s s e t s t r ( F s S e t t i n g s , ” midi . d r i v e r ” , FS MIDI DRIVER ) ;
9 f l u i d s e t t i n g s s e t i n t ( F s S e t t i n g s , ” a u d i o . p e r i o d −s i z e ” , FS BUFFER SIZE
);
61
10 f l u i d s e t t i n g s s e t i n t ( F s S e t t i n g s , ” a u d i o . p e r i o d s ” , FS N BUFFERS) ;
11 f l u i d s e t t i n g s s e t i n t ( F s S e t t i n g s , ” synth . v e r b o s e ” , 1 ) ;
12 f l u i d s e t t i n g s s e t i n t ( F s S e t t i n g s , ” midi . a u t o c o n n e c t ” , 0 ) ;
13
14 /∗ C r e a t e synth i n s t a n c e ∗/
15 t h i s −>FsSynth = n e w f l u i d s y n t h ( t h i s −>F s S e t t i n g s ) ;
16
17 /∗ Load s o u n d f o n t s ∗/
18 /∗ANALOG∗/
19 t h i s −>s F o n t s [ 2 ] . name=” a n a l o g ” ;
20 t h i s −>s F o n t s [ 2 ] . f i l e N a m e=SF ANALOG PATH ;
21 t h i s −>s F o n t s [ 2 ] . i d = f l u i d s y n t h s f l o a d ( t h i s −>FsSynth , t h i s −>s F o n t s
[ 2 ] . fileName , 1 ) ;
22 /∗AMBIANCE∗/
23 t h i s −>s F o n t s [ 1 ] . name=” ambiance ” ;
24 t h i s −>s F o n t s [ 1 ] . f i l e N a m e=SF AMBIANCE PATH ;
25 t h i s −>s F o n t s [ 1 ] . i d = f l u i d s y n t h s f l o a d ( t h i s −>FsSynth , t h i s −>s F o n t s
[ 1 ] . fileName , 1 ) ;
26 /∗COSMIC∗/
27 t h i s −>s F o n t s [ 0 ] . name=” c o s m i c ” ;
28 t h i s −>s F o n t s [ 0 ] . f i l e N a m e=SF COSMIC PATH ;
29 t h i s −>s F o n t s [ 0 ] . i d = f l u i d s y n t h s f l o a d ( t h i s −>FsSynth , t h i s −>s F o n t s
[ 0 ] . fileName , 1 ) ;
30
31 /∗ C r e a t e Audio D r i v e r ∗/
32 t h i s −>FsAudioDriver = n e w f l u i d a u d i o d r i v e r ( t h i s −>F s S e t t i n g s , t h i s −>
FsSynth ) ;
33
34 /∗ I n i t Values ∗/
35 init values () ;
36
37 }
Code 5: CSynth constructor
4.6.2 CProtectedBuffer
The CProtectedBuffer class implements a generic buffer with one mutex and two
condition variables for thread synchronization. It’s a template class, so it is generic and
abstracts from any data type. The way this protected buffer works is by following a
producer-consumer model in a circular buffer. It uses a front counter to keep track of
the position where data was inserted (produced) and a tail counter to track where data
is being removed (consumed) from the buffer. Combining the front and tail with a flag
that indicates if any of those counters have completed a ”lap” (frontOdd and tailOdd)
it is possible to keep track of the used or free space in the buffer.
62
1 t e m p l a t e <typename DataType>
2 c l a s s CProtectedBuffer
3 {
4 private :
5 DataType d a t a B u f f e r [ SIZE ] ;
6 unsigned i n t f r o n t ;
7 unsigned i n t t a i l ;
8 pthread mutex t bufferMutex ;
9 p t h r e a d c o n d t bufferNotEmpty ;
10 pthread cond t bufferNotFull ;
11 b o o l frontOdd ;
12 bool tailOdd ;
13 public :
14 CProtectedBuffer ( ) : f r o n t ( 0 ) , t a i l ( 0 ) , bufferMutex (
PTHREAD MUTEX INITIALIZER) ,
15 bufferNotEmpty (PTHREAD COND INITIALIZER) , b u f f e r N o t F u l l (
PTHREAD COND INITIALIZER) , frontOdd ( f a l s e ) , t a i l O d d ( f a l s e )
16 {}
17 v o i d p u s h B u f f e r ( DataType& toPush ) ;
18 v o i d p o p B u f f e r ( DataType& toPop ) ;
19 } ;
Code 6: CProtectedBuffer interface
CProtected buffer implements two methods: popBuffer and pushBuffer. The push-
Buffer ”produces” elements for the buffer. It starts by locking the mutex and checking
if the buffer is full. If it is indeed full, pushBuffer can’t produce elements, so it waits
for a ”bufferNotFull” signal from the consumer (popBuffer) and unlocks the mutex so
the consumer can use it. When that signal arrives it means that there is at least on
free position, so pushBuffer adds the element to the buffer and increments the front
position. If this position is 0, it means that the front counter has completed a lap, so
the frontOdd flag is activated.
1 t e m p l a t e <typename DataType>
2 v o i d C P r o t e c t e d B u f f e r <DataType > : : p u s h B u f f e r ( DataType& toPush )
3 {
4 p t h r e a d m u t e x l o c k (& t h i s −>b u f f e r M u t e x ) ;
5
6 w h i l e ( t h i s −>f r o n t == t h i s −> t a i l && t h i s −>frontOdd != t h i s −>t a i l O d d )
// While b u f f e r i s f u l l
7 {
8 p t h r e a d c o n d w a i t (& t h i s −>b u f f e r N o t F u l l , &t h i s −>b u f f e r M u t e x ) ; //
Wait f o r NOT FULL s i g n a l
9 }
10
11 i f ( t h i s −>f r o n t == t h i s −> t a i l && t h i s −>frontOdd == t h i s −>t a i l O d d ) //
B u f f e r has a t l e a s t one e l e m e n t
12 {
13 p t h r e a d c o n d s i g n a l (& t h i s −>bufferNotEmpty ) ; // Send BUFFER NOT
63
EMPTY s i g n a l
14 }
15
16 t h i s −>d a t a B u f f e r [ t h i s −>f r o n t ] = toPush ; // Add data t o b u f f e r
17 t h i s −>f r o n t ++, t h i s −>f r o n t &= 0xFF ; // Increment f r o n t
18
19 i f ( ! t h i s −>f r o n t ) // One l a p completed
20 {
21 t h i s −>frontOdd = ! t h i s −>frontOdd ;
22 }
23 p t h r e a d m u t e x u n l o c k (& t h i s −>b u f f e r M u t e x ) ;
24 }
Code 7: CProtectedBuffer push
The popBuffer implements the consumer. If the buffer is empty it waits for a for a
”bufferNotEmpty” signal from the producer. If the buffer has at least one free position
it will send a signal to the producer. When consuming an element, popBuffer increments
the tail position.
1 t e m p l a t e <typename DataType>
2 v o i d C P r o t e c t e d B u f f e r <DataType > : : p o p B u f f e r ( DataType& toPop )
3 {
4 p t h r e a d m u t e x l o c k (& t h i s −>b u f f e r M u t e x ) ;
5
6 w h i l e ( t h i s −>f r o n t == t h i s −> t a i l && t h i s −>frontOdd == t h i s −>t a i l O d d )
// While b u f f e r i s empty
7 {
8 p t h r e a d c o n d w a i t (& t h i s −>bufferNotEmpty , &t h i s −>b u f f e r M u t e x ) ; //
Wait f o r NOT EMPTY s i g n a l
9 }
10
11 i f ( t h i s −>f r o n t == t h i s −> t a i l && t h i s −>frontOdd != t h i s −>t a i l O d d ) //
B u f f e r has a t l e a s t one f r e e s p a c e
12 {
13 p t h r e a d c o n d s i g n a l (& t h i s −>b u f f e r N o t F u l l ) ; // Send BUFFER NOT
FULL s i g n a l
14 }
15
16 toPop = t h i s −>d a t a B u f f e r [ t h i s −> t a i l ] ; // Read data from b u f f e r
17 t h i s −> t a i l ++, t h i s −> t a i l &= 0xFF ; // Increment t a i l
18
19 i f ( ! t h i s −> t a i l ) // One l a p completed
20 {
21 t h i s −>t a i l O d d = ! t h i s −>t a i l O d d ;
22 }
23 p t h r e a d m u t e x u n l o c k (& t h i s −>b u f f e r M u t e x ) ;
24 }
Code 8: CProtectedBuffer pop
64
4.6.3 TLed
TLed class implements the LED thread. It is responsible for sending the IOCTL
commands to the LED’s device drivers. The LED thread acts like a consumer of led
commands, defined in the ledCommand t structure. When constructed, TLed receives
a pointer to the led command buffer of MIDImize.
1 enum lCmd { OSC 1 ON , OSC 1 OFF , OSC 1 BLK , OSC 2 ON , OSC 2 OFF ,
OSC 2 BLK , PWR ON, PWR OFF, PWR BLK } ;
2
3 s t r u c t ledCommand t
4 {
5 i n t note ;
6 lCmd led cmd ;
7 };
8
9 c l a s s TLed
10 {
11 private :
12 C P r o t e c t e d B u f f e r <ledCommand t>∗ cmdBuffer ;
13 pthread t threadId ;
14 p t h r e a d a t t r t threadAttr ;
15 void ∗(∗ job ) ( void ∗) ;
16 public :
17 TLed ( C P r o t e c t e d B u f f e r <ledCommand t>∗ led cmds , v o i d ∗ ( ∗ j o b ) ( v o i d
∗) ) ;
18 int create () ;
19 int join () ;
20 int exit () ;
21 } ;
Code 9: TLed interface and ledCommand t data structure
The job this thread does is implemented by tLed job. When created TLed will
insert the LED device drivers into the kernel using a system call, open the LED’s
device files and run its job inside an infinite loop. The LED thread is a consumer of
LED commands, so it pops a LED command from MIDImize’s LED command buffer
and sends it to the corresponding device driver using an IOCTL system call.
1 v o i d ∗ t L e d j o b ( v o i d ∗ opaque )
2 {
3 C P r o t e c t e d B u f f e r <ledCommand t>∗ m y b u f f e r = ( C P r o t e c t e d B u f f e r <
ledCommand t >∗) opaque ;
4
5 i n t osc1 , osc2 , pwr ;
6 i n t 3 2 t on =1;
7 i n t 3 2 t o f f =0;
8
65
9 system ( ” insmod / e t c /MIDImize/ d e v i c e d r i v e r s / o s c 2 l e d r p i 4 . ko ” ) ;
10 system ( ” insmod / e t c /MIDImize/ d e v i c e d r i v e r s / o s c 1 l e d r p i 4 . ko ” ) ;
11 system ( ” insmod / e t c /MIDImize/ d e v i c e d r i v e r s / p w r l e d r p i 4 . ko ” ) ;
12
13 o s c 1 = open ( ” / dev / o s c 1 l e d ” , O WRONLY) ; // Open o s c i l l a t o r 1 LED
device f i l e
14 o s c 2 = open ( ” / dev / o s c 2 l e d ” , O WRONLY) ; // Open o s c i l l a t o r 2 LED
device f i l e
15 pwr = open ( ” / dev / p w r l e d ” , O WRONLY) ; // Open power LED d e v i c e f i l e
16
17 while (1)
18 {
19 ledCommand t cmd ;
20 m y b u f f e r −>p o p B u f f e r (cmd) ;
21 s w i t c h (cmd . led cmd )
22 {
23 /∗ OSCILLATOR 1 LED ∗/
24 c a s e OSC 1 ON :
25 i o c t l ( osc1 , PWR, ( i n t 3 2 t ∗ ) &on ) ;
26 break ;
27
28 c a s e OSC 1 OFF :
29 i o c t l ( osc1 , PWR, ( i n t 3 2 t ∗ ) &o f f ) ;
30 break ;
31
32 c a s e OSC 1 BLK :
33 i o c t l ( osc1 , BLK, ( i n t 3 2 t ∗ ) &cmd . n o t e ) ;
34 break ;
35
36 /∗ OSCILLATOR 2 LED ∗/
37 c a s e OSC 2 ON :
38 i o c t l ( osc2 , PWR, ( i n t 3 2 t ∗ ) &on ) ;
39 break ;
40
41 c a s e OSC 2 OFF :
42 i o c t l ( osc2 , PWR, ( i n t 3 2 t ∗ ) &o f f ) ;
43 break ;
44
45 c a s e OSC 2 BLK :
46 i o c t l ( osc2 , BLK, ( i n t 3 2 t ∗ ) &cmd . n o t e ) ;
47 break ;
48
49
50 c a s e PWR ON:
51 i o c t l ( pwr , PWR, ( i n t 3 2 t ∗ ) &on ) ;
52 break ;
53
54 c a s e PWR OFF:
55 i o c t l ( pwr , PWR, ( i n t 3 2 t ∗ ) &o f f ) ;
56 break ;
57
58 c a s e PWR BLK:
66
59 i o c t l ( pwr , BLK, ( i n t 3 2 t ∗ ) &cmd . n o t e ) ;
60 break ;
61
62 default :
63 break ;
64 }
65 }
66 }
Code 10: TLed job t data structure
4.6.4 CMidiMize
CMidiMize class is the main class of the system. It is implemented using the sin-
gleton design pattern in order to ensure that there is only one instance of this class
while providing a global access point to that instance. It has a private constructor
and a static method called ”getInstance”. After one instance of this class is created,
this static method will return the instance and doesn’t allow for more instances to be
created.
1 s t r u c t QtWrapper
2 {
3 C P r o t e c t e d B u f f e r <ledCommand t>∗ l e d c t r l ;
4 cSynth ∗ synth [ 2 ] ;
5 bool solo ;
6 };
7
8 c l a s s CMidiMize
9 {
10 private :
11 s t a t i c CMidiMize ∗ i n s t a n c e ;
12 cSynth s y n t h s [ N SYNTHS ] ;
13 TLed l e d t h r e a d ;
14
15
16 CMidiMize ( QtWrapper &QtWrap ) ;
17
18 public :
19 s t a t i c CMidiMize ∗ g e t I n s t a n c e ( QtWrapper &QtWrap )
20 {
21 i f (! instance )
22 {
23 i n s t a n c e = new CMidiMize ( QtWrap ) ;
24 }
25 return instance ;
26 }
27
28 ˜ CMidiMize ( )
67
29 {
30 delete instance ;
31 system ( ”rmmod p w r l e d r p i 4 ” ) ;
32 system ( ”rmmod o s c 1 l e d r p i 4 ” ) ;
33 system ( ”rmmod o s c 2 l e d r p i 4 ” ) ;
34 }
35
36 C P r o t e c t e d B u f f e r <ledCommand t> l e d c m d s ;
37 } ;
Code 11: CMidiMize interface and QT Wrapper structure
In CMidiMize attributes it can be seen that there is a two position array with 2
CSynth objects (the system oscillators), a LED thread, a LED command buffer and
a QT wrapper structure that provides an interface to the system oscillators and LED
command buffer.
As stated before, CMidiMize is the main class of the system and is responsible for
its initialization. The constructor of CMidiMize starts by initializing the QT wrapper
structure, then starts the LED thread, turns the power LED on and sets MIDImize
volume to 0.
68
4.6.5 MidiMizeForm
For example, when the user rotates the volume knob on MIDImize’s UI, that triggers
a signal and calls the respective slot or callback called ”gainDial valueChanged”. That
callback will then process the received volume value and make a system call to change
the system volume.
1 v o i d MidiMizeForm : : o n g a i n D i a l v a l u e C h a n g e d ( i n t v a l u e )
2 {
3 /∗ System Volume − amixer ∗/
4 s t r i n g my volume = t o s t r i n g ( v a l u e ) ;
5 c h a r c o n s t ∗cmd = my volume . c s t r ( ) ;
6 s e t e n v ( ”MDMZ VOLUME” , cmd , 1 ) ;
7
8 system ( ” amixer s e t Headphone $MDMZ VOLUME%” ) ;
9 }
Code 13: Volume change dial callback
The most relevant methods/slots worth mentioning are the Solo Mode button clicked,
MIDI Mode button clicked, Oscillator On and Oscillator Type Changed. The first thing
to do when changing from Solo Mode to MIDI Mode is to check if there’s any note being
played by any of the oscillators, if that’s the case the notes and the LED’s are turned
off. Then the change from Solo Mode to MIDI mode is performed by connecting the
system MIDI port to the oscillator MIDI driver. Each oscillator has a associated MIDI
driver and MIDI router. The MIDI driver is responsible for processing real-time MIDI
events received from the MIDI ports. The MIDI router processes the MIDI events from
the driver and generates control events for the oscillator using a callback. For example,
when a key is pressed on the MIDI keyboard, the MIDI Led handler callback will get
the type of event and the note that was played and make IOCTL system calls to make
the LED’s blink.
1 // about . e x e c ( ) ;
2 }
3
4 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ CHANGE TO MIDI MODE ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
5 v o i d MidiMizeForm : : o n m i d i R b u t t o n c l i c k e d ( b o o l checked )
6 {
7 i f ( checked )
8 {
9 /∗ TURN OFF LED ’ s AND SOUND FROM SOLO MODE ∗/
69
10 i f ( QtWrap . synth [0]−> synthOn )
11 {
12 ledCommand t cmd={55 , OSC 1 OFF } ;
13 QtWrap . l e d c t r l −>p u s h B u f f e r (cmd) ;
14 QtWrap . synth [0]−> n o t e O f f ( 1 , QtWrap . synth [0]−> c u r r e n t n o t e ) ;
15 QtWrap . synth [1]−> s e t O s c i l l a t o r ( ) ;
16 system ( ” a c o n n e c t 24 128 ” ) ;
17 system ( ” a c o n n e c t 24 130 ” ) ;
18 }
19 i f ( QtWrap . synth [1]−> synthOn )
20 {
21 ledCommand t cmd={55 , OSC 2 OFF } ;
22 QtWrap . l e d c t r l −>p u s h B u f f e r (cmd) ;
23 QtWrap . synth [1]−> n o t e O f f ( 1 , QtWrap . synth [1]−> c u r r e n t n o t e ) ;
24 QtWrap . synth [1]−> s e t O s c i l l a t o r ( ) ;
25 system ( ” a c o n n e c t 24 129 ” ) ;
26 system ( ” a c o n n e c t 24 131 ” ) ;
27 }
28
29 /∗ CHANGE TO MIDI MODE ∗/
30 QtWrap . s o l o=f a l s e ;
31
32 QtWrap . synth [0]−> i n i t m i d i ( ) ;
33 QtWrap . synth [1]−> i n i t m i d i ( ) ;
34 init osc1Led midi () ;
35 init osc2Led midi () ;
Code 14: MIDI Mode slot - Change from Solo to MIDI Mode
1 int h a n d l e m i d i e v e n t o s c 1 ( v o i d ∗ data , f l u i d m i d i e v e n t t ∗ e v e n t )
2 {
3 s t a t i c int note count = 0;
4 char type c [ 2 4 ] ;
5 char key c [ 2 4 ] ;
6 ledCommand t cmd , cmd osc , cmd pwr ;
7 QtWrapper ∗ l e d = ( QtWrapper ∗ ) data ;
8
9 /∗ Get p r e s s e d key ∗/
10 i n t key = f l u i d m i d i e v e n t g e t k e y ( e v e n t ) ;
11 s p r i n t f ( ke y c , ”Key : %d\n” , type ) ;
12 /∗ Get MIDI e v e n t type ∗/
13 i n t type = f l u i d m i d i e v e n t g e t t y p e ( e v e n t ) ;
14 s p r i n t f ( t y p e c , ” Event type : %d\n” , type ) ;
15
16 i f ( type ==144)// Note On
17 {
18 i f ( n o t e c o u n t ==0)
19 {
20 cmd = { key , OSC 1 BLK } ;
21 l e d −>l e d c t r l −>p u s h B u f f e r (cmd) ;
22 }
70
23 n o t e c o u n t ++;
24 }
25 e l s e i f ( type ==128) // Note O f f
26 {
27 n o t e c o u n t −−;
28 i f ( n o t e c o u n t ==0)
29 {
30 cmd osc = { key , OSC 1 OFF } ;
31 cmd pwr = { key , PWR ON} ;
32 l e d −>l e d c t r l −>p u s h B u f f e r ( cmd osc ) ;
33 l e d −>l e d c t r l −>p u s h B u f f e r ( cmd pwr ) ;
34 }
35 }
36 e l s e i f ( type ==208) // A f t e r t o u c h
37 {
38 cmd = { key , PWR BLK} ;
39 l e d −>l e d c t r l −>p u s h B u f f e r (cmd) ;
40 }
41
42 /∗ P r i n t key ∗/
43 f l u i d l o g ( FLUID INFO , k e y c ) ;
44 /∗ P r i n t MIDI e v e n t type ∗/
45 f l u i d l o g ( FLUID INFO , t y p e c ) ;
46
47 r e t u r n FLUID OK ;
48 }
Code 15: Oscillator 1 MIDI LED handler
When changing from MIDI mode to Solo mode, the MIDI drivers are disconnected
from the system MIDI ports.
1 }
2 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
3
4 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ CHANGE TO SOLO MODE ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
5 v o i d MidiMizeForm : : o n s o l o R b u t t o n c l i c k e d ( b o o l checked )
6 {
7 i f ( checked )
8 {
9 i f ( ! QtWrap . s o l o ) // MIDI mode
10 {
11 QtWrap . synth [0]−> s t o p m i d i ( ) ;
12 QtWrap . synth [1]−> s t o p m i d i ( ) ;
13 d e l e t e f l u i d m i d i d r i v e r ( t h i s −>o s c 1 L e d D r i v e r ) ;
14 d e l e t e f l u i d m i d i r o u t e r ( t h i s −>osc1LedRouter ) ;
15 d e l e t e f l u i d m i d i d r i v e r ( t h i s −>o s c 2 L e d D r i v e r ) ;
16 d e l e t e f l u i d m i d i r o u t e r ( t h i s −>osc2LedRouter ) ;
17 d e l e t e f l u i d s e t t i n g s ( t h i s −>o s c 1 L e d S e t t i n g s ) ;
18 d e l e t e f l u i d s e t t i n g s ( t h i s −>o s c 2 L e d S e t t i n g s ) ;
71
19 }
20
21 QtWrap . s o l o=t r u e ;
22
23 i f ( QtWrap . synth [0]−> synthOn==f a l s e )
24 {
25 QtWrap . synth [0]−> s e t O s c i l l a t o r ( ) ;
26 }
27
28 i f ( QtWrap . synth [0]−> synthOn )
29 {
30 ledCommand t cmd={QtWrap . synth [0]−> c u r r e n t n o t e , OSC 1 BLK } ;
31 QtWrap . synth [0]−> noteOn ( 1 , QtWrap . synth [0]−> c u r r e n t n o t e , 5 0 )
;
32 QtWrap . l e d c t r l −>p u s h B u f f e r (cmd) ;
33 }
34
35 i f ( QtWrap . synth [1]−> synthOn==f a l s e )
36 {
37 QtWrap . synth [1]−> s e t O s c i l l a t o r ( ) ;
38 }
39
40 i f ( QtWrap . synth [1]−> synthOn )
41 {
42 i f ( QtWrap . synth [1]−> synthOn )
43 {
44 ledCommand t cmd={QtWrap . synth [1]−> c u r r e n t n o t e ,
OSC 2 BLK } ;
45 QtWrap . synth [1]−> noteOn ( 1 , QtWrap . synth [1]−> c u r r e n t n o t e ,
50) ;
46 QtWrap . l e d c t r l −>p u s h B u f f e r (cmd) ;
47 }
48
49 }
50 }
Code 16: Solo Mode slot - Change from MIDI to Solo Mode
72
8 i f ( QtWrap . s o l o ) // SOLO mode
9 {
10 QtWrap . synth [0]−> s e t O s c i l l a t o r ( ) ;
11 ledCommand t cmd={55 , OSC 1 BLK } ;
12 QtWrap . synth [0]−> noteOn ( 1 , 5 5 , 5 0 ) ;
13 QtWrap . synth [0]−> c u r r e n t n o t e = 5 5 ;
14 QtWrap . l e d c t r l −>p u s h B u f f e r (cmd) ;
15 }
16
17 e l s e i f ( ! QtWrap . s o l o ) // MIDI mode
18 {
19 system ( ” a c o n n e c t 24 128 ” ) ; // Connect midi d r i v e r − sound
20 system ( ” a c o n n e c t 24 130 ” ) ; // Connect midi d r i v e r − LED
21 QtWrap . synth [0]−> s e t O s c i l l a t o r ( ) ;
22 }
23 }
24
25 else
26 {
27 QtWrap . synth [0]−> synthOn=f a l s e ; // OSCILLATOR 1 OFF
28
29 i f ( QtWrap . s o l o ) // SOLO mode
30 {
31 ledCommand t cmd={ 5 5 , OSC 1 OFF } ;
32 QtWrap . synth [0]−> n o t e O f f ( 1 , QtWrap . synth [0]−> c u r r e n t n o t e ) ;
33 QtWrap . l e d c t r l −>p u s h B u f f e r (cmd) ;
34 }
35
36 e l s e i f ( ! QtWrap . s o l o ) // MIDI mode
37 {
38 system ( ” a c o n n e c t −d 24 128 ” ) ; // D i s c o n n e c t midi d r i v e r −
sound
39 system ( ” a c o n n e c t −d 24 130 ” ) ; // D i s c o n n e c t midi d r i v e r − LED
40 }
41 }
42 }
43 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
Code 17: Turn oscillator 1 on/off slot
To change the oscillator type, the slot function updates the oscillator type variable
and if a note was playing it guarantees that it keeps playing with the new oscillator
sound.
1 v o i d MidiMizeForm : : o n o s c 1 T r i R b u t t o n t o g g l e d ( b o o l checked )
2 {
3 i f ( checked )
4 {
5 QtWrap . synth [0]−> o s c i l l a t o r=AMBIANCE;
6
7 i f ( QtWrap . synth [0]−> synthOn )
73
8 {
9 QtWrap . synth [0]−> s e t O s c i l l a t o r ( ) ;
10 }
11
12 i f ( QtWrap . synth [0]−> synthOn && QtWrap . s o l o )
13 {
14 QtWrap . synth [0]−> n o t e O f f ( 1 , QtWrap . synth [0]−> c u r r e n t n o t e ) ;
15 QtWrap . synth [0]−> noteOn ( 1 , QtWrap . synth [0]−> c u r r e n t n o t e , 5 0 )
;
16 }
17 }
18 }
Code 18: Change oscillator 1 type slot
74
5 Conclusions and future work
This project management followed the waterfall model that consists in three distinct
phases: Analysis, Design and Implementation. Every phase is very dependant on the
work done at the previous phase, so it’s crucial to pay attention to every detail and to
be very critical with the first phases to prevent potential problems while implementing.
During the development of MIDImize the biggest setback was having the team re-
duced to one element at the middle of the implementation phase. That issue highlighted
the fact that it is almost impossible to predict every future problem while working on the
first development phases. Despite that, the solution adopted to mitigate that problem
was successful and MIDImize was working as intended at the end of the Implementation
phase. Regarding the budget, it exceeded the initial estimation by roughly 11e.
For future work the most important things to consider are: the implementation of
the Solo Mode keyboard and pitch bend and the didactic functions of MIDImize. One
of the problems MIDImize was set to solve was reducing the learning curve associated
with Synthesizers. That was not implemented successfully. It is now obvious that is
a very difficult task and it requires a lot of the users feedback. Since MIDImize is
an open-source project, it is also important to add the Buildroot configuration to the
project GitHub page as well as some instruction on how to install it.
At the end, MIDImize fullfils more than 90% of the requirements set on the Analysis
Phase. It was designed to be upgraded and can be easilly updated with new features
like new sounds, new effects or even different GUI designs.
https://github.com/MariovMesquita/MIDIMize
75
6 Gantt diagram and task division
76