In this tutorial, we're going to make a program which plays tones that you can use to tune a guitar. You will learn how to:
Creació d'un projecte a l'Anjuta
Create a simple GUI with Anjuta's UI designer
Use GStreamer to play sounds
You'll need the following to be able to follow this tutorial:
An installed copy of the Anjuta IDE
Basic knowledge of the C++ programming language
Abans de començar a programar, heu de crear un projecte nou a l'Anjuta. L'Anjuta crearà tots els fitxers necessaris per, més endavant, construir i executar el codi. És molt útil per així mantenir-ho tot junt.
Inicieu l'Anjuta i feu clic a
Trieu
Make sure that
Feu clic a
#include ]]>
This is a very basic C++ code setting up GTKmm. More details are given below; skip this list if you understand the basics:
The three #include
lines at the top include the config
(useful autoconf build defines), gtkmm
(user interface) and iostream
(STL). Functions from these libraries are used in the rest of the code.
The main
function creates a new window by opening a GtkBuilder file (
Afterwards it calls a few functions which set up and then run the application. The kit.run
function starts the GTKmm main loop, which runs the user interface and starts listening for events (like clicks and key presses).
This code is ready to be used, so you can compile it by clicking
Press
A description of the user interface (UI) is contained in the GtkBuilder file. To edit the user interface, open
The layout of every UI in GTK+ is organized using boxes and tables. Let's use a vertical
Select a
Now, choose a
While the button is still selected, change the
Repeat the above steps for the other buttons, adding the next 5 strings with the labels A, D, G, B, and e and the names button_A, etc.
Save the UI design (by clicking
GStreamer is GNOME's multimedia framework — you can use it for playing, recording, and processing video, audio, webcam streams and the like. Here, we'll be using it to produce single-frequency tones. GStreamermm is the C++ binding to GStreamer which we will use here.
Conceptually, GStreamer works as follows: You create a pipeline containing several processing elements going from the source to the sink (output). The source can be an image file, a video, or a music file, for example, and the output could be a widget or the soundcard.
Between source and sink, you can apply various filters and converters to handle effects, format conversions and so on. Each element of the pipeline has properties which can be used to change its behaviour.
An example GStreamer pipeline.
To use GStreamermm, it has to be initialised. We do that by adding the following line of code next to the
Gtk::Main kit(argc, argv);
line in
Gst::init (argc, argv);
While we are on it, also make sure that the
In this simple example we will use a tone generator source called audiotestsrc
and send the output to the default system sound device, autoaudiosink
. We only need to configure the frequency of the tone generator; this is accessible through the freq
property of audiotestsrc
.
To simplify the handling of the pipeline we will define a helper class Sound
. We do
that in
m_pipeline;
Glib::RefPtr m_source;
Glib::RefPtr m_sink;
};
Sound::Sound()
{
m_pipeline = Gst::Pipeline::create("note");
m_source = Gst::ElementFactory::create_element("audiotestsrc",
"source");
m_sink = Gst::ElementFactory::create_element("autoaudiosink",
"output");
m_pipeline->add(m_source);
m_pipeline->add(m_sink);
m_source->link(m_sink);
}
void Sound::start_playing (double frequency)
{
m_source->set_property("freq", frequency);
m_pipeline->set_state(Gst::STATE_PLAYING);
/* stop it after 200ms */
Glib::signal_timeout().connect(sigc::mem_fun(*this, &Sound::stop_playing),
200);
}
bool Sound::stop_playing()
{
m_pipeline->set_state(Gst::STATE_NULL);
return false;
}
]]>
The code has the following purpose:
In the constructor, source and sink GStreamer elements (Gst::Element
) are created, and a pipeline element (which will be used as a container for the other two elements). The pipeline is given the name "note"; the source is named "source" and is set to the audiotestsrc
source; and the sink is named "output" and set to the autoaudiosink
sink (default sound card output). After the elements have been added to the pipeline and linked together, the pipeline is ready to run.
start_playing
sets the source element to play a particular frequency and then starts the pipeline so the sound
actually starts playing. As we don't want to have the annoying sound for ages, a timeout is set up to stop the pipeline
after 200 ms by calling stop_playing
.
In stop_playing
which is called when the timeout has elapsed, the pipeline is stopped and as such there isn't
any sound output anymore. As GStreamermm uses reference counting through the Glib::RefPtr
object, the memory
is automatically freed once the Sound
class is destroyed.
We want to play the correct sound when the user clicks a button. That means that we have to connect to the signal that is fired when the user clicks the button. We also want to provide information to the called function which tone to play. GTKmm makes that quite easy as we can easily bind information with the sigc library.
The function that is called when the user clicks a button can be pretty simple, as all the interesting stuff is done in the helper class now:
start_playing (frequency);
}
]]>
It only calls the helper class we defined before to play the correct frequencies. With some more clever code we would also have been able to directly connect to the class without using the function but we will leave that to use as an exercise.
The code to set up the signals should be added to the main()
function just after the
builder->get_widget("main_window", main_win);
line:
get_widget("button_E", button);
button->signal_clicked().connect (sigc::bind(sigc::ptr_fun(&on_button_clicked),
329.63, &sound));
]]>
At first we create an instance of our helper class that we want to use now and declare a variable for the button we want to connect to.
Next, we receive the button object from the user interface that was created out of the user interface file. Remember that button_E is the name we gave to the first button.
Finally we connect the clicked signal. This isn't fully straightforward because this is done in a fully type-safe
way and we actually want to pass the frequency and our helper class to the signal handler.
sigc::ptr_fun(&on_button_clicked)
creates a slot for the on_button_clicked
method
we defined above. With sigc::bind
we are able to pass additional arguments to the slot and in this
case we pass the frequency (as double) and our helper class.
Now that we have set up the E button we also need to connect the other buttons according to their frequencies: 440 for A, 587.33 for D, 783.99 for G, 987.77 for B and 1318.5 for the high E. This is done in the same way, just passing a different frequency to the handler.
All of the code should now be ready to go. Click
If you haven't already done so, choose the
Si teniu algun problema amb el programa d'aprenentatge, compareu el codi amb el codi de referència.
Many of the things shown above are explained in detail in the GTKmm book which also covers a lot more key concept for using the full power of GTKmm. You might also be interested in the GStreamermm reference documentation.
Here are some ideas for how you can extend this simple demonstration:
Have the program automatically cycle through the notes.
Make the program play recordings of real guitar strings being plucked.
To do this, you would need to set up a more complicated GStreamer pipeline which allows you to load and play back music files. You'll have to choose decoder and demuxer GStreamer elements based on the file format of your recorded sounds — MP3s use different elements to Ogg Vorbis files, for example.
You might need to connect the elements in more complicated ways too. This could involve using GStreamer concepts that we didn't cover in this tutorial, such as pads. You may also find the
Automatically analyze notes that the user plays.
You could connect a microphone and record sounds from it using an input source. Perhaps some form of spectrum analysis would allow you to figure out what notes are being played?