You are on page 1of 77

University of Minho

MIEEIC
Embedded Systems

Open-Source MIDI synthesizer module

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.1 Market Research . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.1.1 Target Users . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.1.2 Similar Products in the Market . . . . . . . . . . . . . . . . . . 7

2.2 Problem statement analysis . . . . . . . . . . . . . . . . . . . . . . . . 11

2.2.1 Why Open-Source? . . . . . . . . . . . . . . . . . . . . . . . . . 12

2.3 System overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2.3.1 Functional requirements . . . . . . . . . . . . . . . . . . . . . . 14

2.3.2 Non functional requirements . . . . . . . . . . . . . . . . . . . . 14

2.3.3 Technical constraints . . . . . . . . . . . . . . . . . . . . . . . . 15

2.3.4 Non technical constraints . . . . . . . . . . . . . . . . . . . . . . 15

2.4 System architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

2.4.1 Hardware architecture . . . . . . . . . . . . . . . . . . . . . . . 16

2.4.2 Hardware Specification . . . . . . . . . . . . . . . . . . . . . . . 16

2.5 Software architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2.6 Local System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

2.6.1 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

2.6.2 Use cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

2.6.3 State Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

2.6.4 Sequence Diagrams . . . . . . . . . . . . . . . . . . . . . . . . . 21

2.7 Estimated Budget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

1
3 Design Phase 23

3.1 Theoretical foundations . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3.1.1 MIDI Protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3.1.2 Additive Sound Synthesis . . . . . . . . . . . . . . . . . . . . . 25

3.2 Hardware Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

3.2.1 Raspberry Pi 4 Model B . . . . . . . . . . . . . . . . . . . . . . 28

3.2.2 7” LCD Screen . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

3.2.3 Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

3.2.4 Connection Layout . . . . . . . . . . . . . . . . . . . . . . . . . 29

3.2.5 Test Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

3.2.6 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

3.3 Software Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

3.3.1 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

3.3.2 Third-party Libraries . . . . . . . . . . . . . . . . . . . . . . . . 32

3.3.3 Device Drivers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

3.3.4 Tasks Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

3.3.5 Task Synchronization . . . . . . . . . . . . . . . . . . . . . . . . 34

3.3.6 Task Priority . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

3.3.7 Flowcharts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

3.3.8 Class Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

3.3.9 GUI Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

3.3.10 Test Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

4 Implementation Phase 49

2
4.1 Changes to the design . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

4.2 Hardware implementation . . . . . . . . . . . . . . . . . . . . . . . . . 50

4.2.1 LED circuit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

4.2.2 Enclosure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

4.3 Buildroot configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

4.4 Device drivers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

4.5 Graphical User Interface . . . . . . . . . . . . . . . . . . . . . . . . . . 58

4.6 MIDImize Application . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

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

5 Conclusions and future work 75

6 Gantt diagram and task division 76

List of Figures

1 OP-1 Teenage Engineering . . . . . . . . . . . . . . . . . . . . . . . . . 7

2 Arturia MicroBrute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

3 Waldorf Rocket Synthesizer . . . . . . . . . . . . . . . . . . . . . . . . 9

4 Behringer Crave . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

5 Example of a GRP Synthesizer with a cluttered interface . . . . . . . . 11

6 Entity relationship diagram . . . . . . . . . . . . . . . . . . . . . . . . 13

3
7 System overview diagram . . . . . . . . . . . . . . . . . . . . . . . . . . 14

8 Hardware architecture diagram . . . . . . . . . . . . . . . . . . . . . . 16

9 Software architecture diagram . . . . . . . . . . . . . . . . . . . . . . . 17

10 Use Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

11 State Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

12 Sequence Diagram: Changing element parameters in the GUI . . . . . . 21

13 Sequence Diagram: Playing MIDI note . . . . . . . . . . . . . . . . . . 22

14 Example of a MIDI Byte . . . . . . . . . . . . . . . . . . . . . . . . . . 24

15 Basics of additive synthesis . . . . . . . . . . . . . . . . . . . . . . . . . 26

16 Example of additive synthesis . . . . . . . . . . . . . . . . . . . . . . . 27

17 Raspberry Pi 4 Model B . . . . . . . . . . . . . . . . . . . . . . . . . . 28

18 7” LCD with capacitive touchscreen . . . . . . . . . . . . . . . . . . . . 29

19 Connections Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

20 Task Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

21 Thread Priorities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

22 Protected Buffer Push flowchart . . . . . . . . . . . . . . . . . . . . . . 36

23 Protected Buffer Pop flowchart . . . . . . . . . . . . . . . . . . . . . . 38

24 MIDI Thread flowchart . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

25 Message Processing Thread flowchart . . . . . . . . . . . . . . . . . . . 40

26 Sound Generation Thread flowchart . . . . . . . . . . . . . . . . . . . . 41

27 Audio Output Thread flowchart . . . . . . . . . . . . . . . . . . . . . . 42

28 Class Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

29 Example of a question box . . . . . . . . . . . . . . . . . . . . . . . . . 45

30 GUI Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

4
31 Alternative GUI Layout . . . . . . . . . . . . . . . . . . . . . . . . . . 47

32 LED circuit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

33 Closed enclosure with mounted touchscreen . . . . . . . . . . . . . . . 51

34 Enclosure without the screen and the sliding back door . . . . . . . . . 52

35 Enclosure cable routing hole . . . . . . . . . . . . . . . . . . . . . . . . 52

36 Buildroot - ALSA utils . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

37 Buildroot - ALSA and fluidsynth . . . . . . . . . . . . . . . . . . . . . 53

38 Buildroot - Qt5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

39 Buildroot - Qt5 configuration . . . . . . . . . . . . . . . . . . . . . . . 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 will be a portable synthesizer module with an LCD touchscreen and


a MIDI USB interface where you can plug in any MIDI compatible controller. The
touchscreen interface will help and guide the user achieving the desired sounds as well
as providing information about the different knobs and buttons, shortening the learning
curve.

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

2.1 Market Research

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.

2.1.1 Target Users

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.

2.1.2 Similar Products in the Market

Some examples of already existing synthesizers available to buy:

Synthesizers with built-in keyboards

OP-1

Figure 1: OP-1 Teenage Engineering

7
Features:

• Built in LCD;

• Built-in Sampler;

• Included Sequencer;

• Capable of Handling Multiple Inputs;

• Controller mode to become a MIDI controller with two-octave keyboard.

Price: 1299 euros

MicroBrute

Figure 2: Arturia MicroBrute

Features:

• Built in Oscillator;

• Included Filters;

• Modulation Matrix;

• Sequencer;

• Syncable LFO.

Price: 245 euros

8
Synthesizers modules

Rocket

Figure 3: Waldorf Rocket Synthesizer

Features:

• Small form factor;

• MIDI input and output;

• Included Arpeggiator;

• LFO for modulation;

• 8 Oscillators;

• Included envelopes for VCA, VCF and Sync.

Price: 329 euros

9
Crave

Figure 4: Behringer Crave

Features:

• Included Arpeggiator;

• Built-in Sequencer;

• Oscillator;

• A few included filters.

Price: 159 euros

10
2.2 Problem statement analysis

MIDImize is set to be an affordable alternative to synthesizer modules found on the


market today. In order to be a viable and appealing alternative, MIDImize will provide
the same features as a conventional synthesizer while offering new features that few or
no products on the market are offering.

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.

Figure 5: Example of a GRP Synthesizer with a cluttered interface

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.

Another important feature of MIDImize is the ability to be played without any


controller. The device operates in one of two modes: MIDI mode or Solo mode.

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.

2.2.1 Why Open-Source?

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.

Figure 6: Entity relationship 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.

Figure 7: System overview diagram

In order to design this system, there is the need to specify all the constraints and
requirements.

2.3.1 Functional requirements

• Synth sound generation;

• Communication with MIDI over USB (MIDI mode);

• Operation with no MIDI input (Solo mode);

• Drive speakers through the audio jack;

2.3.2 Non functional requirements

• Robust 3D printed case;

14
• Low cost;

• Open-Source;

• Small footprint;

• User-friendly interface;

• Interface helps user achieving the desired sounds;

2.3.3 Technical constraints

• Runs on a Raspberry Pi 4 Model B;

• Cross Platform Development - Buildroot;

• Embedded Linux - Rpi;

• Must be a Cyber Physical System;

• C and C++ Programming Language;

• Use device drivers;

• Raspberry Pi lacks a good DAC;

2.3.4 Non technical constraints

• Deadline until January 13th;

• Limited budget;

• Team of 2 people;

• Global electronics component shortage may delay the assembly of the device;

• Team has no experience with the MIDI protocol;

• Team has no experience with synthesizers;

• Team has no experience designing and modelling for a 3D printer;

• Compliance with industry standards and certification;

• External speakers may require amplification;

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.

2.4.1 Hardware architecture

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).

Figure 8: Hardware architecture diagram

2.4.2 Hardware Specification

For each of the aforementioned hardware components, a specification must be pro-


vided before the implementation stage. These are some of the key components:

• Raspberry pi 4 Model B - used as the main processing unit;

• AC/DC Adapter - 5V 3A adapter to power the Raspberry Pi and the LCD;

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.

• MIDI Keyboard - Any USB MIDI compatible keyboard.

2.5 Software architecture

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.

Figure 9: Software architecture diagram

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.

Event Action Type


Power On Initialize the system and Asynchronous
select the mode
MIDI Controller Plugged Change from Solo Mode Asynchronous
in to MIDI Mode
Note Played Process note and output Asynchronous
the sound
User Presses GUI element Update element in the Asynchronous
Synth Module

2.6.2 Use cases

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.

Figure 10: Use Cases

2.6.3 State Diagram

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.

Figure 11: State Diagram

20
2.6.4 Sequence Diagrams

Figure 12: Sequence Diagram: Changing element parameters in the GUI

21
Figure 13: Sequence Diagram: Playing MIDI note

2.7 Estimated Budget

In the following table the individual cost of the components have been layed out.

Quantity Description Price (in e)


1 LCD Touch Screen 60
1 Raspberry Pi 4 Model B 58
w/Power Adapter
1 Module Enclosure 7

• 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;

• As mentioned previously, the enclosure will be 3D printed. Although it has a


small form factor, it is set to be very robust to endure day-to-day use.

22
3 Design Phase

3.1 Theoretical foundations

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.

3.1.1 MIDI Protocol

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).

Figure 14: Example of a MIDI Byte

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

In order to implement MIDImize a detailed description of the hardware will be


made. All the hardware will be encapsulated inside a 3D printed structure. The main
goal of the structure is to hide all MIDImize’s hardware, and make it more robust and
portable. Only the screen will be visible to the user and the USB-C power port, a USB
port and the audio jack will be accessible from the outside.

3.2.1 Raspberry Pi 4 Model B

To be able to handle the processing operations, a development board will be used.


The board chosen is the Raspberry Pi 4 Model B.
It’s most important features are:

• 4Gb LPDD4-3200 SDRAM;

• 40-pin GPIO header;

• 2 USB 3.0 ports;

• 2 USB 2.0 ports;

• 2 × micro-HDMI ports (up to 4kp60 supported);

• 4-pole stereo audio and composite video port;

• Micro-SD card slot for loading operating system and data storage;

• 5V/2.5A DC power input;

Figure 17: Raspberry Pi 4 Model B

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:

• Operating Voltage: 5V;


• Operating Current: 160mA;
• Maximum Operating Current: 1200mA;
• Refresh Frequency: 43Hz;
• Resolution: 1024x600;
• Viewing angle: 60º/70º/70º/70º;
• Number of touch points: 5;
• Video interface: HDMI (standard size HDMI);
• Touch interface: USB (micro usb);

Figure 18: 7” LCD with capacitive touchscreen

3.2.3 Architecture

3.2.4 Connection Layout

As previously said, MIDImize uses a 7” LCD display with a capacitive touchscreen


to allow the user to interact with the GUI. For that, it uses the HDMI port for the

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.

Figure 19: Connections Layout

3.2.5 Test Cases

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.

The most important things to test are:

Test Case Expected Output


Connect HDMI screen to rasp Image from the board is accurately
shown
Touch the corners of the display The coordinates received match the
corners
Touch the screen in 5 places at the same The board receives 5 coordinates
time
Connect USB from screen to rasp Touch screen appears as a pointing
device
Plug in a MIDI keyboard to a raspberry The MIDI keyboard is recognized as a
pi USB port MIDI USB device
Plug amplified speakers to the raspberry The speakers reproduce the sound
pi audio jack and use ALSA drivers to
output a simple sound

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

To enable the implementation phase, several tools were chosen:

• Buildroot - for cross platform development;


• VS Code - as the default IDE;
• QT Creator - enable cross platform development of the GUI;
• Git - to keep track of the code and keep it safe;
• Meld - to manage git code collisions and merges.

3.3.2 Third-party Libraries

• 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.

Overtone is a very complex audio environment aimed at exploring everything music


related, from synthesizers to instrument building. It works hand-in-hand with the
previously mentioned SuperCollider engine.

QT will be used to develop the GUI in a cross-platform development system.

32
Pthreads, or POSIX threads defines an API to create and manipulate threads.

3.3.3 Device Drivers

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.

MIDI Device Driver functions:

• read();

• open();

• init();

• exit():

3.3.4 Tasks Overview

MIDImize will be implemented following a multitasking approach.

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

3.3.5 Task Synchronization

To allow communication among tasks and guarantee synchronization MIDImize will


use two main objects:

• 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.

• Protected Synth structure


This proteced structure provides one mutex for data protection.

3.3.6 Task Priority

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.

Figure 21: Thread Priorities

34
3.3.7 Flowcharts

• CProtectedBufferPush

This flowchart describes how a push to the protected buffer works.


As previously stated, the protected buffer follows a producer-consumer model. In
this case, the CProtectedBufferPush is the producer. It is also a circular buffer,
using a front and a tail number to keep track of the used/free space. When an
element is produced the front is incremented, and when an element is consumed,
the tail is incremented.
Firstly, the buffer mutex is locked to protect its data. If the buffer is full
(Front==Tail && FrontOdd != tailOdd) it will wait for a “Not full” signal and
unlock the mutex so the consumer can use it. If the buffer has at least one element
(Front==Tail && FrontOdd==TailOdd) it will send a signal that the buffer is
not empty.
After this, the element is added to the buffer in the front position and the front is
incremented. If the front is on the position 0 after being incremented it means that
it completed a “lap” of the circular buffer and is now on the begging position. In
this case the FrontOdd Boolean is toggled to indicate that the front has completed
a lap. In the end the buffer mutex is unlocked.

35
Figure 22: Protected Buffer Push flowchart

36
• CProtectedBufferPop

This flowchart describes how a pop to the protected buffer works.


As previously stated, the protected buffer follows a producer-consumer model. In
this case, the CProtectedBufferPop is the consumer. It is also a circular buffer,
using a front and a tail number to keep track of the used/free space. When an
element is produced the front is incremented, and when an element is consumed,
the tail is incremented.
Firstly, the buffer mutex is locked to protect its data. If the buffer is empty
(Front==Tail && FrontOdd == tailOdd) it will wait for a “Not empty” signal
and unlock the mutex so the producer can use it. If the buffer has at least one
free space (Front==Tail && FrontOdd!=TailOdd) it will send a signal that the
buffer is not full.
After this, the element is taken from the buffer in the tail position and the tail is
incremented. If the tail is on the position 0 after being incremented, it means that
it completed a “lap” of the circular buffer and is now on the begging position. In
this case the TailOdd Boolean is toggled to indicate that the tail has completed
a lap.
In the end the buffer mutex is unlocked.

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.

Figure 24: MIDI Thread flowchart

39
• MessageProcessingThread

This flowchart describes how the MessageProcessingThread works. The Message-


ProcessingThread is the consumer of MIDI messages.
Firstly, it pops a MIDI message from the protected buffer and converts the note
to the respective frequency. After that it updates the Synth values with the data
from the MIDI message.

Figure 25: Message Processing Thread flowchart

40
• SoundGenerationThread

This flowchart describes how the SoundGenerationThread works. The Sound-


GenerationThread is the producer of wave points.
Firstly, it locks the Synth mutex to protect its data, then reads the Synth data
and unlocks the mutex. It then generates a sound wave respective to that Synth
data and pushes a wave point to the protected buffer.

Figure 26: Sound Generation Thread flowchart

41
• Audio Output Thread

This flowchart describes how the AudioOutputThread works. The AudioOutput-


Thread is the consumer of wave points.
This thread is responsible for popping wave points from the protected buffer and
output the sound using the audio device driver.

Figure 27: Audio Output Thread flowchart

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 tMessageProcessingThread is responsible for grabbing a message from the pro-


tected buffer and updating the Synth object attributes. It provides the methods to
create, join and exit the thread. It also provides the method read-message() that reads
a MIDI message from the protected buffer and the method note-to-frequency() that
converts a note to the respective frequency.

The tSoundGenerationThread is responsible for generating a sound wave according


to the Synth data. It provides the methods to create, join and exit the thread and the
method generate() that generates a wave point and pushes it to the protected buffer.

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

3.3.9 GUI Layouts

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

3.3.10 Test Cases

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.

Here are some of the most important tests for:

47
Solo Mode:

Test Case Expected Output


Select a sine wave and a frequency from Sine wave with the selected frequency is
the GUI reproduced by the speakers

MIDI Mode:

Test Case Expected Output


Play a note on the MIDI keyboard The speakers output the note that was
played
Play several notes on the MIDI keyboard The speakers output the notes in the
order they were played

Mode independent:

Test Case Expected Output


Move the ADSR sliders on the GUI The output sound is changed at the
while playing a note same time the ADSR sliders are moved
Move the filter cutoff slider in the GUI The output sound is changed as the
while playing on the keyboard cutoff slider is moved
Turn the volume up and down on the The output volume goes up and down as
GUI the knob in the GUI is turned up or
down

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.

4.1 Changes to the design

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

4.2.1 LED circuit

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.

Figure 32: LED circuit

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.

Figure 33: Closed enclosure with mounted touchscreen

51
Figure 34: Enclosure without the screen and the sliding back door

Figure 35: Enclosure cable routing hole

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.

Figure 36: Buildroot - ALSA utils

Figure 37: Buildroot - ALSA and fluidsynth

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.

Figure 38: Buildroot - Qt5

Figure 39: Buildroot - Qt5 configuration

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

4.5 Graphical User Interface

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.

4.6 MIDImize Application

The implemented classes for MIDImize are:

• 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.

1 CMidiMize : : CMidiMize ( QtWrapper &QtWrap) : l e d t h r e a d (& t h i s −>led cmds ,


tLed job )
2 {
3 /∗ QT Wrapper i n i t ∗/
4 QtWrap . l e d c t r l=&t h i s −>l e d c m d s ;
5 QtWrap . synth [0]=& t h i s −>s y n t h s [ 0 ] ;
6 QtWrap . synth [1]=& t h i s −>s y n t h s [ 1 ] ;
7 QtWrap . s o l o=t r u e ;
8 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
9
10 /∗ S t a r t LED t h r e a d ∗/
11 t h i s −>l e d t h r e a d . c r e a t e ( ) ;
12 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
13
14 /∗ PWR LED ON∗/
15 ledCommand t cmd={55 , PWR ON} ;
16 t h i s −>l e d c m d s . p u s h B u f f e r (cmd) ;
17 /∗ ∗∗∗∗∗∗∗∗∗∗ ∗/
18
19 system ( ” amixer s e t Headphone 0%” ) ; // START MUTED
20 }
Code 12: CMidiMize constructor

68
4.6.5 MidiMizeForm

The MidiMizeForm class is automatically created by QT Creator after designing


the UI with QT Designer. A QT graphical app works with signals and slots. A signal
is emitted when a QT event occurs and a slot is the callback function called by that
signal.

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

To turn a oscillator on or off, if MIDImize is in solo mode, it simply updates the


oscillator state variable and plays the current note. If it is in MIDI mode, it connects
or disconnects the oscillator MIDI driver from the system MIDI ports if the user wants
to turn it on or off respectively.

1 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ TURN OSCILLATOR 1 ON / OFF ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/


2 v o i d MidiMizeForm : : o n o s c 1 P b u t t o n c l i c k e d ( b o o l checked )
3 {
4 i f ( checked )
5 {
6 QtWrap . synth [0]−> synthOn=t r u e ; // OSCILLATOR 1 ON
7

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.

The project can be found on the following GitHub page:

https://github.com/MariovMesquita/MIDIMize

75
6 Gantt diagram and task division

Figure 41: Task division

Figure 42: Gantt diagram

76

You might also like