KHS Guitar Tuner Pro is an Android and iOS application that can assist guitarists with a variety of different tuning setups. The app can play notes within the selected tuning to use as a reference, and it uses incoming audio from the microphone to give musicians a visual indicator of what pitch is being played. It also contains a metronome with multiple timing options, making it useful for the whole practice session.
The application was written with Flutter to share a single implementation across multiple platforms. It was built in accordance with the Bloc design pattern for code organization.
Application Structure
KHS Guitar Tuner Pro consists of two main components, the Tuner and the Metronome. The app displays a navigation bar at the bottom that allows the user to switch between the associated screens.
Tuner Screen
The primary functionality of the app is housed on the Tuner screen, where the user can select their target tuning for a guitar and receive guidance in adjusting their string tension to reach the desired pitch.
The application monitors any incoming notes via the device’s microphone and displays the observed note with a meter at the top of the screen. The note is highlighted in green when the pitch matches an expected note or in yellow when the pitch is incorrect. This gives the user visual feedback for not only whether a given string is tensioned correctly but also whether an incorrect pitch is too high or low for their target.
A set of buttons is also displayed for playing the desired notes through the device’s speakers. Once a user selects which tuning they prefer, these buttons update with the corresponding notes and provide the user with reference sounds to compare against for each string.
Metronome Screen
KHS Guitar Tuner Pro also provides a metronome to use for practice sessions. The user can configure the top and bottom values of their time signature and even adjust the tempo in beats per minute.
Once activated, the app will play an audible tick and flash the play/pause button green as each beat occurs, yielding a brighter green and a louder tick for beats emphasized by the time signature.
Flutter Overview
Flutter is a software development toolkit based on the Dart programming language, used for deploying a shared codebase to multiple platforms. With Flutter, the same application code is used to compile separate native applications for each platform being targeted.
This allows the application to still utilize platform-specific features and high performance without requiring manual rewrites or optimizations for each one. Instead, developers can focus on contributing to a single codebase, with all the complexity of different operating systems and architectures being handled for them automatically.
The behavior of Flutter applications is defined by widgets. These are particular types of classes that are responsible for rendering subsets of content to the screen or handling specific actions from the user interface. Widgets can also keep track of the application state, giving them more contextual awareness of what’s happening as they make behavioral decisions.
Since widgets are the core components of Flutter applications, each piece of functionality is typically composed of multiple widgets nested together in a tree-like structure. When the state of an application changes, the impacted portions of the widget tree are rebuilt to reflect the state transition. For this reason, the organization of this application state and the relationships between widgets are important things to consider in Flutter.
Architectural Overview
Within the opinionated directory structure of a Flutter application, KHS Guitar Tuner Pro adheres to the Bloc design pattern. At its core, Bloc is a state management library prioritizing the separation of an application’s business logic from its presentation layer. It achieves this by encapsulating an application’s state within designated business logic classes called Blocs
.
A Bloc
class is instantiated with its own internal state that can be observed but not directly modified by its consumers. Manipulations of that state are accomplished through the API of the class, either by directly invoking its methods or by submitting events that it listens to. The added formality of accessing the application’s business logic makes for a clearer boundary between the layers of the application, as well as a cleaner testing approach for the silos of logic.
With the flutter_bloc package, we can easily convert these design principles into Flutter idioms.
Flutter Bloc
In order for a Flutter application to function, its Widget
classes must have access to any relevant information required to render their content. With the business logic and state of KHS Guitar Tuner Pro encapsulated into Blocs
, we needed a way to expose them to our widgets without dissolving that separation of responsibilities between the presentation and logic layers. The flutter_bloc
package provides ways to manage this, primarily with the BlocBuilder
and BlocProvider
widget classes.
The BlocBuilder
widget is responsible for rebuilding its content in response to state updates being emitted from its assigned Bloc
instance. Recall that a Bloc
class manages its own state internally to keep business logic organized away from the presentation layer.
As such, when a Bloc
class receives an event and performs its action, a new state is typically emitted as a result to reflect the change. When the BlocBuilder
widget receives this updated state, it can execute its builder
function to render fresh content to the screen.
A BlocProvider
can be used to make a single Bloc
instance available to any of its child widgets. The key word here is “available,” meaning that the children are able to access the methods or state of the Bloc
instance without directly owning it.
This allows the BlocProvider
to serve as a wrapper for widgets that need to consume similar business logic. When used in tandem with the BlocBuilder
widget, it allows multiple widgets to all consume the same state updates and submit events to the same Bloc
instance.
By combining these widgets in a Flutter application, we have fine-grain control over widget rendering behavior and business logic invocation, all while keeping the implementation of our logic isolated to a different layer of the project. This was helpful for the KHS Guitar Tuner Pro application as its sound processing logic grew in complexity.
Processing Sound
Analog-to-Digital Conversion (ADC)
Processing sound on a mobile device requires audio-to-digital conversion. First, the audio signal is observed by the device’s microphone. Then, the analog signal is sampled at uniform intervals and each sample is converted into a digital representation, using a finite number of bits.
Since these binary values must fit into a finite space, this means that there are a finite number of possible digital values that can be used to represent a sample. The number of bits used equates to an upper bound on the accuracy of the digital representation of the audio, and it is referred to as the “resolution” of the system. In short, the higher your resolution, the more accurately your digital data represents the analog signal.
Since KHS Guitar Tuner Pro aims to measure the frequency of the observed audio signals, the rate at which the audio is sampled also comes into play. As an example, imagine a ball bouncing to a height of 1 meter each second. If you measure the ball’s height twice each second and record the values, you can observe the periodic increase and decrease and infer how frequently it bounces:
[1, 0, 1, 0, 1, 0, 1, 0, 1]
However, if you matched the ball’s frequency and only measured it once per second, your recorded values no longer show that periodic increase or decrease:
[1, 0, 1, 0, 1, 0, 1, 0, 1]
If we decrease our rate of measurement even further and check less than once per second, our measurements might completely miss some of the bounces:
[1, 0, 1, 0, 1, 0, 1, 0, 1]
This principle applies to measuring sound waves in the same way. If we are not sampling the audio frequently enough, our measurements could be missing some of the oscillations in the original source. According to the Nyquist-Shannon sampling theorem, the fastest oscillation our system can measure must include at least two samples. This means that whatever frequency we decide on for our sample rate, the highest measurable audio frequency will be half that amount.
So in summary, using more bits per sample means that we can keep track of their values more accurately, and capturing more samples per second lets us better understand the frequency of the original audio.
While the obvious solution is to increase the resolution and sample rate as much as possible, this means that the system has to expend more effort to compute the extra data. In the case of a mobile application like KHS Guitar Tuner Pro, a balance must be reached. The application still has enough information to operate reliably without overwhelming the device’s processor or draining its battery.
Pitch Detection
Once the sample rate and resolution decisions of the ADC process have been reached, the application now has enough information to infer the frequency, or “pitch,” of the original audio signal. However, calculating a frequency is impossible to do with the data from an individual sample, so our sampling data must be aggregated together in order to make any meaningful inferences.
There is also the problem of noise, or unwanted data, to be accounted for since KHS Guitar Tuner Pro might be used in an environment where background sounds are present. The Pitch Detection process aims to resolve these problems.
First, the audio samples are sent through a noise gate. Its responsibility is to suppress any values which are below a certain volume threshold, to reduce the likelihood of ambient noise from factoring into the pitch calculations. These on
or off
states are transitioned gradually by the noise gate to maintain a “smoother” set of data.
Then, the sampling data is passed through a Fast Fourier Transformation (FFT) algorithm to measure the amplitudes of all frequencies observed within the data set. Conceptually, the output of this algorithm is like a graph of the audible frequency range, with a continuous line plotting the volumes at each pitch being detected.
So, if the sound from a guitar’s A string was recorded producing a frequency of 440 Hz, that part of the graph would show a spike relative to the other frequencies. The peaks of these spikes are located as we iterate through the dataset, and their positions and heights are noted.
Finally, the application can examine this peak data to determine which fundamental frequency is being observed. This can be tricky for stringed instruments, as our A string example above will also probably have smaller peaks all along its harmonic series (880 Hz, 1320 Hz, etc.). Each peak’s amplitude, as well as the presence of any harmonic peaks, are weighed to determine the final frequency observed by the application.
Dart Isolates
Due to the complexity of the Pitch Detection logic above, it became necessary for KHS Guitar Tuner Pro to perform some of its logic in a separate thread. For this, an Isolate
is spawned to perform the noise gate and FFT calculations without blocking user interface actions.
Isolates
allow for serialized messaging between threads. As such, the main thread is able to still perform the lighter part of the workflow (like recording the microphone audio) and then delegate the heavier work to the background process. The owning Bloc
class in the main thread also establishes an event listener for the child thread’s return message, allowing it to submit an event as soon as the child completes its calculations.
This parallelization not only saves time by executing the computation concurrently, but it allows the main thread to stay responsive for user interaction and maintain a smooth experience.
Conclusion
Flutter remains a powerful tool for writing applications for multiple platforms. It pairs nicely with the Bloc design pattern, allowing for a clean separation between presentation and business logic and high testability of components.
Once these organizational principles have been set, adding even complicated sets of logic becomes easier. Performance-heavy workflows like sound processing can still be a challenge when developing for mobile devices, but with proper organization of code and distribution across multiple threads, this can all be achieved without sacrificing the responsiveness of the user interface.
KHS Guitar Tuner is live now in both the Apple and Google Play app stores.
Download for iOS Download for Android