[Gambas-user] Extern howto/tutorial

Doriano Blengino doriano.blengino at ...1909...
Mon Aug 23 18:33:58 CEST 2010


Hi,

I finally wrote the tutorial about using external declarations.

It is a long text and perhaps difficult, but I didn't find any other way 
to put it in a simpler form: the problem of interfacing gambas to 
external libraries, from the perspective of a gambas user, is not simple.

I wrote it using zim, a nice wiki editor. I attach what I exported from 
the document, in both html and "Txt2Tags" (?) format.

Feel free to modify it as needed, to include it in the wiki, or 
whatever. I attach two projects related to the explanation, one short 
and simple and one much more complex, but more funny.

This document regards Gambas2. When it will be corrected/approved, I 
will port projects and documentation to Gambas3.

Now some technical question.

In the Drum Machine project, after having verified that it works 
correctly using pointers for read/write, I tried to create a new class 
which should be a gambas representation for an alsa event (which is a C 
struct). It does not work, and I don't know how to debug it (well, I 
could, using gdb or similar, but I was looking for a more direct way). 
My idea was to point a pointer to the instance of the class CEvent, and 
then dump a few bytes to see them. But gambas does not let me to assign 
a pointer to a class instance ("wanted integer, found object").

Moreover, in the gambas 3 documentation there is nothing that makes me 
think that "write #pointer, ..." is no more supported. Either gambas3 
supports it (but it does not seems to me), or the documentation is wrong.

Another thing is that if I declare a constant as a byte, and then try to 
"write #pointer, my_byte_constant", 4 bytes are written instead of just 
one. I had to mention it in the document.

I am really curious to give a try to gambas3 structures, but I want 
firstly terminate with gambas2.

Best regards,
Doriano Blengino


-------------- next part --------------
A non-text attachment was scrubbed...
Name: Gambas2-DrumMachine-0.1.3.tar.gz
Type: application/x-tgz
Size: 15831 bytes
Desc: not available
URL: <http://lists.gambas-basic.org/pipermail/user/attachments/20100823/56a86023/attachment.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: External-gb2-0.0.2.tar.gz
Type: application/x-tgz
Size: 17609 bytes
Desc: not available
URL: <http://lists.gambas-basic.org/pipermail/user/attachments/20100823/56a86023/attachment-0001.bin>
-------------- next part --------------

   [ Prev ] [ [1]Index ] [ Next ]
     _________________________________________________________________

                                     Home

   Created sabato 10 luglio 2010

   How to interface Gambas to external libraries

INTRODUCTION

   There are lots of shared libraries available in a Linux system, capable of
   doing a lot of useful things, and many of those libraries can be used from
   Gambas using some of its features.

   The  first step to do is to find a suitable library to use for a given
   purpose; not all the libraries can be used, but the vast majority can. The
   prerequisite is that the library is written in plain C. Most libraries are
   written in C, while others are written in C++ and, perhaps, still other
   languages. This document will focus only about C libraries.

   Once the intended library is found, its general logic must be understood in
   order to determine what is needed, and how this new stuff will be used by
   the final program. The full documentation of the library, and possibly some
   example, should be readed carefully. Libraries are not programs, and hence
   their philosophy is quite different. A program tends to contain only the
   subroutines  required  to do its job, while a library is what the name
   implies:  a  collection of subroutines, to be used many times, by many
   different programs. It is not uncommon to find, inside libraries, two or
   more subroutines which do the same thing, in a slightly different way.
   Libraries sometimes are written keeping in mind that they will be used by
   different languages, not only C: python, ruby, ocaml, many others, Gambas
   included. Libraries tend to encapsulate the details of a task inside a
   "handle",  in  a  way similar to a Gambas object; but, they don't have
   properties and methods - everything is carried out by calling functions
   which these handles are passed to. If you think at a Gambas class with three
   properties and four methods, an external library implementing the same thing
   will have at least seven (three+four) subroutines, and probably a couple
   more to create and destroy the object. Other than this, there is not a big
   difference from setting a property and calling a function, the latter is
   simply  a  bit longer to write. To search for what we need, we must be
   prepared to read a lot of documentation, too often badly written (hey,
   apropos, how well do we document our software?).

THE EXTERN DECLARATION

   Extern  declaration  is  simple. It is like a normal Gambas subroutine
   declaration, but preceded by "EXTERN". The EXTERN keyword says to gambas
   that the body of the procedure is not defined by the gambas program we are
   writing, but somewhere else (an external library). We must also specify
   which  library  to  use:  this  is done by the clause "IN libraryXXX".
   Alternatively, you can use a separate LIBRARY statement: all the subsequent
   extern declarations will refer to this statement. Either "IN library" or
   "LIBRARY  xxx" can specify a version number after a colon, and this is
   recommended.

   Let's see an example, choosen because of its simplicity:
LIBRARY "libc:6"
EXTERN getgid() AS Integer

   These two lines say that a function named "getgid" exists in the library
   "libc" version 6. This function takes no parameters, and returns an integer
   (the group ID).

   The same thing can be written like this:
EXTERN getgid() AS Integer IN "libc:6"

   Another example, slightly more complicated. This time we have parameters,
   and the last thing to say about the formal declaration:
' int kill(pid_t pid, int sig);
EXTERN killme(pid AS Integer, sig AS Integer) AS Integer IN "libc:6" EXEC "kill
"

   The first line (the comment) shows the original declaration, and the second
   line the gambas one. We can note a number of things. First, what does the
   original declaration mean? It means "a function called kill returns an int,
   and it accepts two parameters. The first is named pid and its type is pid_t;
   the second is named sig and its type is int". Contrary to Gambas, the C
   language puts the type of a variable before the variable instead of after.
   Second, what in Gambas is called "Integer", in C is called "int". Third,
   what is "pid_t"? It's a type; we can understand it because it is written in
   a place where a type specifier is expected, and because ends with "_t"
   (underscore t).
   Third, a new clause EXEC "kill" is used in the Gambas declaration. This is
   necessary because we want to use a function named kill, but KILL is a name
   already  used  by  Gambas.  So,  in  Gambas, we must name the function
   differently, but anyway we must indicate its true name inside the library.
   The declaration says "I declare an external function named killme, but its
   real name is kill". I chose the name killme because in the attached gambas
   example this function is used to kill the running program.

   To be sincere I noticed that, even without renaming the function from kill
   to killme, the program was working the same. May be that this has something
   to do with case sensitivity - C is sensitive, and Gambas not, so there is
   still a difference between kill (lowercase) and KILL (uppercase). Anyway,
   when there is a possible name conflict, it is best to use this renaming
   technique.

WARNING --- END OF THE EASY PART

   (just joking)

   At this point, several things must be noted. Most of the suitable libraries
   are written in C, which is a different language than Gambas. We will need to
   know at least a little of C declarations, in order to translate them to
   gambas. Referring to the last example, one could ask why I translated the
   pid_t type to integer. The simple and correct answer is "because on my
   system  the  pid_t type is actually an integer". This answer is really
   correct, but must be explained better, talking about agrumes (?). We can
   think  about  lemons and oranges, which both are agrumes, and are very
   similar: they weight more or less the same, and often can be interchanged;
   you can eat them directly, or squeeze them to drink their juice, but it is
   unlikely that you will put orange juice on your fried fish. In C, this is
   expressed by the fact that it is unlikely that you want to use the kill()
   function passing it an arbitrary integer. Surely, you will pass a process
   identifier (PID), which actually is an integer, but it is indicated more
   precisely as pid_t. In my motivation, I also said "on my system the pid_t
   type...". Yes, on my system - on most system, the type pid_t is an integer,
   but this could be different.

   The final answer can be found by typing these two commands in a terminal:
grep -r pid_t /usr/include/* |grep "#define"
grep -r pid_t /usr/include/* |grep typedef

   which will show the involuted way types are managed in C. This argument is
   way too complicated to go further; giving that Gambas runs on Linux, and
   presumably on desktop systems, we can consider that all the parameters
   passed  to  a function, and returned by it, will be either integer, or
   pointers - which are integer too, or strings - which are pointers that are
   integers. There can be also floating point numbers - float and double. The
   following  table  lists  some  of  the  types you can encounter in a C
   declaration, and the suitable type to be used in gambas:
C type                  Gambas type
int                     ->      integer
long                    ->      long
float           ->      single
double          ->      float
xxxx*           ->      pointer (the asterisk means exactly "pointer")
char*           ->      pointer - but see later
other types     ->      integer or pointer (depends on the declaration); see la
ter

   We  will start to briefly introduce pointers, which are little used in
   Gambas. A pointer is an integer, but used very differently. The thing that
   more closely resembles a pointer in Gambas is a class instance. When you
   create, say, a Form in Gambas, a lot of data is stored somewhere in memory.
   That memory will hold all the specific settings of the form: its caption,
   its color, the list of all its children, and so on. The address of that
   block of memory is returned to your program, and stored into the variable
   which refers to the just created form:
MainForm = NEW Form()

   That variable MainForm is really a pointer: in only 4 (or 8) bytes it tracks
   a lot of data, stored somewhere in memory at a specific location (address).
   The  memory  is a long sequence of cells (bytes), each identified by a
   progressive number. A pointer contains the identifying number of a cell of
   memory (its address). In C, pointers are used for two reasons: the first is
   that  passing only an address (a pointer contains an address), is much
   quicker than passing a lot of data; this is the same reason why Gambas
   instance variables like MainForm are similar to pointers.

   The second reason is when the called function should modify the variable we
   pass. For example, if we in Gambas wrote:
INPUT a ' where a is an integer

   in C we would write:
void input(int *a);
...
input(&a);

   The reason is that we want our INPUT command to fill our variable "a". In C,
   we must call input() and say to it where our variable is, in order to let it
   fill the variable. The ampersand "&" takes the address of the variable, and
   passes it to the function. The declaration of input() says "int *a", which
   states that "a" is not an integer, but a pointer to an integer, ie, the
   parameter says where to find the value, not the value itself.

GAMBAS IMPLEMENTATION OF POINTERS

   Gambas has the datatype Pointer, and a set of operations suitable for it. To
   use a pointer, a normal declaration is required like any other variable.
   Then, a value must be assigned to it. When using a normal variable, often
   you can assign a literal, for example you can write "a=3". With pointers,
   this  is not advisable. A pointer gives access to any cell location in
   memory, but you should know in advance what location you are interested in,
   and  that  location  must be the correct one for the intended purpose,
   otherwise Gambas or the operating system will get angry. This is much the
   same  as  saying  that you can not write "MainForm = 3". You can write
   "MainForm = NEW Form()", or "MainForm = AnotherExistentForm", or "MainForm =
   NULL". So, a direct assignment to a pointer will always be to NULL, to
   another pointer, or to a call to some function returning a pointer. Just as
   a class instance variable like MainForm.

   Sometimes an external function returns a pointer, and this pointer will be
   required in order to invoke subsequent calls to the external library. This
   case is much like creating a Form, and using its reference to operate on the
   form itself. In this case, the data behind (pointed by) the pointer is said
   to be "opaque": we can not see through something opaque, so we don't know,
   and we don't want to know. This is the simplest case; an example about this
   is the LDAP library. The first thing to do to interact with LDAP is to open
   a connection to a server. All the subsequent operations will be made on the
   connection created by a specific call. Things go like this:
LIBRARY "libldap:2"
PRIVATE EXTERN ldap_init(host AS String, port AS Integer) AS Pointer
PRIVATE ldapconn as Pointer
...
ldapconn = ldap_init(host, 389)
IF ldapconn = NULL THEN error.Raise("Can not connect to the ldap server")

   As  already  seen, a LIBRARY is specified. Then, an EXTERN function is
   declared; this function is the one which must be called in order to do
   anything with ldap. The last two lines are the ones that, when executed,
   will  open  the connection and store its handle, or instance, for this
   connection. In this specific case, ldap_init() returns NULL if something
   goes wrong, so we can test for NULL to raise an error. Once obtained a
   handle to the connection, this handle must be specified on every subsequent
   call to the ldap library. For example, to delete an entry in the database,
   the following must be used:
PRIVATE EXTERN ldap_delete_s(ldconn AS Pointer, dn AS String) AS Integer
...
PUBLIC SUB remove(dn AS String) AS Integer
DIM res AS Integer
res = ldap_delete_s(ldapconn, dn)

   Unfortunately, things are not always so simple. One of the reasons C uses a
   pointer, is to let the subroutine write some data in the location indicated
   by the calling parameters. Remaining in the initialization of a library,
   ALSA for example is different. To initiate a dialog with the alsa sequencer,
   a handle for the sequencer is needed. The C declaration for this function
   is:
int snd_seq_open(snd_seq_t **seqp, const char * name, Int streams, Int mode);

   Hep! what is this "snd_seq_t **seqp"? We know that the asterisk is used to
   indicate a pointer - so what could mean a double asterisk? It's easy: a
   pointer to a pointer. This function snd_seq_open() uses the pointer notation
   to fill a value; this value is a pointer itself. Differently from the case
   of LDAP, where the function ldap_init() returns only one value, here this
   function returns two values. The return result of the function is an error
   code - all the ALSA functions use this scheme. A return result of zero means
   success. So to return more than a value, the function can only write some
   data to some location we specify, by using a pointer. The value it writes
   has type pointer, so the notation "double pointer" is used. So far so good.
   But can we translate this to Gambas? Yes and no. We need a pointer, and this
   is not a problem. Then we must take the address of this pointer, in order to
   obtain "a pointer to a pointer". Gambas3 can do that, Gambas2 can not.

   Let see the simpler way, only available in Gambas3. The VarPtr() function
   returns the address of a variable or, in other words, a pointer to that
   variable - and its name says so: VAR-PTR, "variable pointer". In gambas3 we
   would write:
PRIVATE EXTERN snd_seq_open(Pseq AS Pointer, name AS String, streams AS Integer
, mode AS Integer) AS Integer
...
PRIVATE AlsaHandler as Pointer
...
err = snd_seq_open(VarPtr(AlsaHandler), "default", 0, 0)

   The EXTERN declaration says that snd_seq_open() expects a pointer, which is
   true: snd_seq_open() expects a pointer to a pointer, which is anyway a
   pointer. So we declare a variable Alsahandler as pointer, and pass its
   address using VarPtr() which returns a pointer to the variable.

   In Gambas 2 this is not possible - we don't have VarPtr(). We must anyway
   declare a variable to hold the handle, like before, but then we can not get
   its address, or a pointer to it. We will attack the problem from another
   side. We need to find a location in memory to pass to alsa and, after that,
   go  to  peek in that location. In Gambas 2 the only way is the Alloc()
   function. By using Alloc(), we reserve a piece of memory somewhere, and
   obtain its address. This address is what we need to pass to snd_seq_open():
   a pointer contains an address. Well, can we start to write something? Yes:
' int snd_seq_open(snd_seq_t **seqp, const char * name, Int streams, Int mode);
PRIVATE EXTERN snd_seq_open(Pseq AS Pointer, name AS String, streams AS Integer
, mode AS Integer) AS Integereger
PRIVATE AlsaHandler as Pointer
...
DIM err AS Integer
DIM ret AS Pointer
ret = Alloc(4)  ' 4 is the size of a pointer in 32-bit systems; 8 for 64-bit sy
stems
err = snd_seq_open(ret, "default", 0, 0)

   When we want to open the connection and obtain a handler, we reserve some
   memory and pass its address, in order to have snd_seq_open() write useful
   data there. But then, how can we read that location to retrieve the handler?
   Here come the functionality of pointers in gambas. Pointers can work like
   streams - you can read from them and write to them. Actually, the memory of
   the computer is a file of memory cells, right? We can read a value from a
   pointer with:
READ #ret, AlsaHandler

   At this point, we succeeded. It's a little like an Odissey, but it is worth!
   We only must release the memory we reserved with Alloc(), so the Odissey is
   not yet over. That memory has been used in a temporary way, and we could
   neglect it, but if this operation was made many many times in a program, the
   program  continues to eat memory. Normally Gambas has automatic memory
   management, but in this case it cannot help because it doesn't know what we
   are doing with the memory, so we are responsible to free the memory when we
   are done with it:
Free(ret)

   There  are  other  reasons  to  use a pointer. Take the declaration of
   getloadavg(), a nice function that tells us how much our CPU has been busy
   in the last minute:
int getloadavg(double loadavg[], int nelem);

   This nice C language can even pass arrays to functions? Yes. And try to
   guess how it does it? Pointers again...
   In this case, the array passed to the function will be filled with one or
   more values, each signifying a different kind of load average; each value
   will be put in consecutive locations in the array. But C is not smart enough
   to know how big an array is, so the function can not know how many values to
   write. We have to tell the function, through the "nelem" parameter. To make
   it short, the correct declaration for this situation is this:
EXTERN getloadavg(ploadavg AS Pointer, nelem AS Integer) AS Intege      r

   We  need  to pass a pointer, because the function getloadavg expects a
   pointer, even if this could not be obvious by looking at its declaration.
   The pointer must point to free ram, because the function will fill the
   memory  pointed to by this pointer. Then, we will read the values and,
   lastly, we will fre the memory. Ax example usage is:
PUBLIC SUB get_load() AS Float
  DIM p AS Pointer
  DIM r AS Float
p = Alloc(8)
IF getloadavg(p, 1) <> 1 THEN
  Free(p)
  RETURN -1   ' error
ENDIF
READ #p, r
Free(p)
RETURN r
        END

   The subroutine is straightforward: we allocate 8 bytes because a gambas
   float is 8 bytes long. Then we call the getloadavg(), which will fill these
   8 bytes. Whether the operation succeeds or not, we must free the allocated
   memory. But, If the operation succeeded, first we must read the memory. This
   is why we have two "free(p)" in the subroutine. A more elegant way could be
   to use a FINALLY clause, but this way we are more close to the C spirit...

   getloadavg() returns the number of values read. Asking for only one value,
   it is legitimate to interpret a return result different from one as an
   error. If we asked for three, and only obtained two, we would have had a
   strange  situation  - something in between from a correct result and a
   failure. This and other funny things can be seen when trying to use some
   historical interfaces. For example, in some version of Unix there is not a
   clear  method  to read a file name. The function returns the number of
   character written, but no indication that the name is shorter than that. So
   you are only sure to have read the full name when you passed a buffer longer
   than the function result. But you have the function result *after* the call,
   not before! The typical usage is to take an arbitrary value, say 256, and do
   the first try. If it fails, you add another 256 bytes, and try again. And so
   on...

   Back to our getloadavg(), anyway. We used Alloc(8) because a gambas float is
   8 bytes long. And we used a gambas float in order to interface with a C
   double. But where is stated that a C double is 8 bytes long? In fact, there
   are out there machines where a double is 10 bytes. This is a serious issue,
   because the above subroutine will not work. We couuld allocate more memory,
   perhaps 64 bytes instead of 8: I am pretty sure that no computer exists wich
   use more than 64 bytes for a floating point number. But anyway, trying to
   read a 8-bytes value out of a 64-bytes value would yeld a nonsense. Perhaps
   is better to let a program crash, instead of giving the impression that it
   works. One question could arise... how can a C program work on so many
   different architectures? The answer is the following: because a new, perhaps
   different architecture, must have an omogeneous set of kernel, include files
   and  compiler. In a real C program you will never see a statement like
   "alloc(8)", but instead something like "alloc(sizeof(double))". The compiler
   knows the size of a double, and the keyword "sizeof" puts the knowledge of
   the compiler into the source program.

More on pointers

   Some better explanation is needed, at this point. The instruction READ
   #ret,... reads something from the location pointed to by the pointer "ret".
   It  is important to stress once again that this kind of things must be
   designed carefully. Working badly with pointers is one of the most common
   cause of failure of C programs and, when using pointers, Gambas can do no
   differently. In this case is easy, because we made our job in a few lines in
   a row.

   The semantics of the READ instruction in pointers resembles the one of
   STREAM, but with an important difference: while the stream is advanced
   automatically after a read or a write, the same operation on pointer does
   not. If our memory contained two variables to be read, one after the other,
   we had to advance the pointer by ourselves:
READ #mypointer, var1_4byte
mypointer += 4
READ #mypointer, var2_4byte

   As you can see, it is possible to treat a pointer like an integer. Using
   this mechanism, one can walk forward and back in memory to emulate what in C
   are called "struct". A "C struct" is a group of heterogeneous variables put
   side by side, which then can be treated like a single variable. Its closest
   counterpart in Gambas is, again, a class. Structures are often referenced by
   a pointer, especially when they are to be passed to a function. We will se
   later an alternative method to implement this in gambas, but now we are
   talking about pointers, so we will finish this topic. The C language has
   also "unions", which are an unknown thing to Gambas, and therefore they must
   be emulated using pointers (not completely true). Unions are composed of two
   or  more variables that share the same memory: writing to one variable
   modifies implicitly the others too: they are overlapped. The reason for this
   is to describe in a unique type different layouts. By combining struct and
   union,  complex  configurations can be generated, and this layouts are
   difficult not only to manage, but even to understand. To give an example, we
   will talk again about the ALSA sequencer. The sequencer works with events
   (mostly notes to be played) that have a time stamp to indicate "when" these
   events are to be played or carried out. This time stamp can be expressed in
   ticks, which is the traditional way related to the metronome. Ticks are
   normal integers. But ALSA goes further, and permits to use real-time time
   stamps, a much more precise indication, useful to synchronize music with
   other  things  (video, for example). This measurement is more precise,
   therefore it needs more memory to hold the bigger precision (two integers).
   So there are events having time specified with 4 bytes (an integer), and
   events having time specified with 8 bytes. They could have used simply two
   fields, respectively of 4 and 8 bytes, one after the other. But by using a
   union, they saved 4 bytes. The real memory reserved for time stamp is 8
   byte, big enough to hold either of the two values, but at a logic level
   these two values are mutually exclusive. All this is handled automatically
   by the C compiler. When playing with unions in Gambas, we must do all this
   by ourself.

A Gambas drum machine

   A concrete example about all we have seen until now is the ALSA library: a
   simple, very basic drum machine will be implemented in Gambas. First of all,
   what is a drum machine? It is a machine which emulates the combination of a
   drum player and a drum set. Many musicians use it, especially those who
   produce music all by themself. In the Gambas world, this is accomplished by
   using ALSA. ALSA is the Advanced Linux Sound Architecture, and its aim is to
   offer a complete set of functions to produce sounds and, hence, music. From
   the point of view of a computer, generic sound and music are two different
   things. If you play an MP3 file, ALSA will move the speakers as directed by
   the MP3 data, without knowing or analyzing anything. We are interested in
   another kind of interface - the sequencer interface. A sequencer copes with
   "events", which are "played" at the right times, using suitable parameters
   (or properties) for the event. If we think at a piano player, we can see
   that  he presses his keys, one or more together, at different moments.
   Simply, every keypress it's an event. The three most important things when
   pressing a key on a piano are: 1) when; 2) which key; 3) how strong. If you
   want to play two notes at the same time, you create two events having the
   same "timestamp". If you want to play a chord, you create three events
   having  the  same time, three different notes, and (probably) the same
   strongness. Then, you feed these events to the sequencer, and it will send
   them to something else which will produce the sounds. The sequencer does not
   care about producing sound: this can be produced by some software, or be
   outputted by a MIDI interface to some external musical instrument. If,
   instead of a piano, you say "I want trumpets", the three notes will be
   played by three trumpets. This interface does not specify "how long the
   notes play": they will sound until another event will say to stop. So a
   single note is actually done with two events: a NOTE-ON and a NOTE-OFF. In
   the case of drums, a note identifies a different piece of percussion: bass
   drum, snare drum, cymbals, maracas, bells, even whistles and much more.

   Music and computers have much in common. For example, a typical musical
   measure is divided in 4 quarters. Is the number four uncommon in computers?
   Keys on piano are numbered, and the strongness ("velocity") of a keypress
   can be expressed by a number, as well as the duration of a note. There are
   other values involved, for example the force a flute player uses to blow in
   his instrument (after the note has been started), but we will not go so
   deep. Only let me say that a good sequencer, combined with good hardware,
   can simulate surprisingly well an entire orchestra.

   The simple drum machine has a grid on the screen, and every cell of the grid
   represents a note: its row number specifies the note to be played (on a drum
   machine, different notes correspond to different pieces, or instruments).
   The column of a cell represents the time when the note will be played. The
   grid contains two measures which are played over and over - this is enough
   to construct a normal rythm. Every measure is divided in 4 quarters, and
   every quarter is divided further in four 16th's. The top row is a visual
   ruler, and the leftmost column is used to hear an instrument. Clicking in a
   cell toggles an "o" marker; to play the pattern click the button "Play
   grid". Other buttons produce some other sound, just to show simpler things
   like chords, legato's, arpeggio.

   To have the program produce sounds, the correct client/device and port (alsa
   terminology) must be written in the first two lines of FMain.class, and
   depends on the hardware installed. Issuing an "aconnect -ol" in a terminal
   shows the suitable devices. If a software synthetizer is present, like
   Timidity, probably it will show as "client 128". The MIDI out device could
   be number 16. The port number can probably be always 0. Another way to find
   out the correct numbers is to use an already working sequencer or MIDI
   player, like Kmidi, and peek at its midi configuration.

   The main reason to analyze this program is to look at a complete interface
   with an external library. Most of the issues have been presented already,
   but an important part not yet covered is how to cope with C structures using
   pointers.
   Instead of using pointers, an alternative way is to use declare variables in
   a class, and then pass an instance of that class to an external function;
   this is not covered here: the method would be better and clearer, but the
   pointers are more versatile. A yet better approach is possible in gambas3,
   which has native structures.

   Because the program is very alsa-specific, we will skip everything but the
   "event structure". Once all the things required by alsa are done (opening
   alsa, creating queues, ports, starting them and so on), only remains to
   construct events and send them to alsa, which will play them at the correct
   time. An event is defined by alsa like this:
snd_seq_event_type_t   type
unsigned char   flags
unsigned char   tag
unsigned char   queue
snd_seq_timestamp_t   time
snd_seq_addr_t   source
snd_seq_addr_t   dest
union {
   snd_seq_ev_note_t   note
   snd_seq_ev_ctrl_t   control
   snd_seq_ev_raw8_t   raw8
   snd_seq_ev_raw32_t   raw32
   snd_seq_ev_ext_t   ext
   snd_seq_ev_queue_control_t   queue
   snd_seq_timestamp_t   time
   snd_seq_addr_t   addr
   snd_seq_connect_t   connect
   snd_seq_result_t   result
}

   The first lines, before "union", are common to every kind of events; in
   fact, they contain the event "type", some "flags", a "tag", the "queue"
   where to enqueue the event, the "source" (who created the event?) and the
   "dest"  (to  whom  send  this  event?).  Let's  look at the firt line:
   "snd_seq_event_type_t type". The field is named "type", and its type is
   "snd_seq_event_type_t type". So we must inspect the documentation to find
   out how this type is made. We find:
typedef unsigned char snd_seq_event_type_t

   The line above says that "snd_seq_event_type_t" is an alias for "unsigned
   char". An unsigned char is a byte.
   The next three fields in the struct are flags tag and queue, all of type
   unsigned char, hence byte.
   Then the timestamp "time" is declared as "snd_seq_timestamp_t"; searching
   again for declaration, we find that it is a union containing either a midi
   tick (an unsigned int) or a struct which is composed by two unsigned int.
   The net result is that the length of this field is 2 unsigned ints, or 8
   bytes on 32-bit systems. The first part of an event is composed, by our
   point of view, of the following fields:
type_of_event                   a single byte
flags                                   a single byte
tag                                     a single byte
queue                           a single byte
timestamp, composed of:
  tick (int) or tv_sec          an integer
  tv_nsec                               an integer

   If  we  want  to fill the field "tick", we must point a pointer to the
   beginning of the event memory, then advance the pointer by 4 bytes, then
   write to the pointer the intended value (an integer).

   The CAlsa class allocates memory for just an event:
PUBLIC SUB alsa_open(myname AS String)
          ...
          ...
  ' alloc an event to work with. It is global to avoid alloc/dealloc burden
          ev = Alloc(SIZE_OF_SEQEV)

   and then manipulates this memory over and over before passing the event to
   alsa. The subroutine prepareev() clears the event and fills the common part.
   Here is its declaration:
PRIVATE SUB prepareev(type AS Byte, flags AS Byte, ts AS Integer) AS Pointer
        DIM p AS Pointer
        DIM i AS Integer

   The parameters of the function reflect what we are interested in - for
   example, we are not interested in the "tag" field, so we don't pass a value
   for it. The first step is to clear the event, to make sure that no unwanted
   data is there from before:
' clear the event
p = ev
FOR i = 1 TO SIZE_OF_SEQEV
  WRITE #p, 0, 1
  INC p
NEXT

   The pointer "p" is pointed to the beginning of the event with "p = ev". With
   a for-next a stream of zeroes is written out. The instruction "WRITE #p, 0,
   1" writes in the memory pointed by #p; it writes the value 0, using 1 byte.
   We must specify "1" (ie 1 byte), because the second parameter (0) is an
   integer constant, so gambas thinks it should write 4 bytes (or 8) - we force
   it to write a single byte. If instead of using a constant ("0"), we used a
   variable of type byte, gambas would have know the size of the variable (1
   byte), and we could have avoided to specify the size. But beware! This works
   with variables, but not with constants (a gambas 2 bug, I suppose).

   A better algorithm to clear the event would be to write 4 bytes at a time,
   and reduce the loop to 1/4. A still better way would be to simply rewrite
   fields we are interested in, and clear only the fields we know that are
   dirty.

   After clearing the event, we start to fill the relevant fields. Again, we
   point our pointer "p" to the correct place (we moved it, remember?), write a
   value, and move the pointer afterward:
p = ev
WRITE #p, type
p += 1          ' now p points to the flag field

   Notice that the "WRITE #p" has a slightly different syntax. This time a
   variable is written, so there is no need to tell gambas how many bytes to
   write. We want to write a single byte, and the variable "type" is 1 byte
   long.

   The rest of the routine is a repetition of what we have already seen. At the
   point of writing the timestamp, which is a C union, there is the following
   code:
WRITE #p, ts  ' timestamp
p += 4

   This  single  instruction  writes  the  first  of  the two integers of
   "snd_seq_timestamp_t time". Then:
ts = 0
WRITE #p, ts  ' 2^ part (realtime event)
p += 4

   Well, these three lines are not needed. We cleared all the memory before, so
   there is no need to set any field to zero. But we should anyway move the
   pointer. The two previous blocks of code could be as follows:
WRITE #p, ts  ' timestamp
p += 8

   The subroutine prepareev() is called by noteon() and noteoff(), which then
   continue to fill the event with own data. The noteon() subroutine is this:
PUBLIC SUB noteon(ts AS Integer, channel AS Byte, note AS Byte, velocity AS Byt
e)
  DIM p AS Pointer
  DIM err AS Integer
p = prepareev(SND_SEQ_EVENT_NOTEON, 0, ts)
WRITE #p, channel
INC p
WRITE #p, note
INC p
WRITE #p, velocity
err = snd_seq_event_output_buffer(handle, ev)

   The subroutine accepts a timestamp "ts", which says -when- to play the note,
   and refers to the time when the queue was started. A timestamp of zero, or
   anyway  a  value  less  than  the current time of the queue, is played
   immediately. If the timestamp is greater than the current queue time, then
   the  event will be played in the future, when the queue will reach the
   correct time. But alsa can also use relative timestamps, setting a flag in
   the event. The gambas CAlsa class uses this possibility embedding it in the
   timestamp directly. If the ts parameter to the prepareev() is set to a
   negative value, then the routine inverts its sign and sets the relative
   flag. In the main program, the routine btArpeggio_Click() produces three
   notes in succession by using this possibility:
PUBLIC SUB btArpeggio_Click()
  alsa.noteon(0, 0, 60, 100)
alsa.noteoff(-100, 0, 60, 100)
alsa.noteon(-100, 0, 64, 100)
alsa.noteoff(-200, 0, 64, 100)
alsa.noteon(-200, 0, 67, 100)
alsa.noteoff(-300, 0, 67, 100)
alsa.flush
        END

   To subroutine starts the first note at time 0, which means "now". After 100
   ticks, the note is shut off, and a new one is started (timestamp=-100 means
   "100 ticks after 0 -- 100 ticks after now"). And so on for the next notes.

   Given that playing notes is the most common task, and that to play a note
   one must create a note-on and a note-off, the CAlsa class implements a
   routine to do that with a single call. The following routine:
' 32 notes having their duration less than their spacing
' "staccato": every note terminates well before the next begins
PUBLIC SUB btStaccato_Click()
  DIM i AS Integer
FOR i = 1 TO 32
  ' relative timestamp=10, 20, 30... successive steps by 10
  ' but the notes have duration=5, not 10
  alsa.playnote(-10 * i, 0, 60 + i, 100, 5)
NEXT
alsa.flush
        END

   uses a single call to playnote(), which in turn generates internally two
   events.

   Lastly, to be precise, here is an explanation of the drum machine algorithm.

   The whole thing is to produce a stream of events, which will be played
   later. We must prepare a bunch of events in advance, so the hardware has
   data to work on. But a drum machine can be kept on for a long time, and we
   can not buffer all the needed events - we must supply a certain number of
   events  ahead  of  time, not too much and not too little. The internal
   "pointer" of the drum machine always is a musical measure ahead of the sound
   we are hearing. We can not predict reliably "when" new data will be needed,
   because the sequencer consumes the events basing itself on a timer that can
   be different from our. Without a feedback from the sequencer, it is nearly
   impossible to keep in sync. The problem is even more difficult if we want to
   have a visual feedback on what the sequencer is playing in a given moment.
   This is solved by adding, in the event stream, some event whose purpose is
   not to make sounds, but to come back to us: an echo. The sequencer receives
   this added events, and sends them back to us at the correct moment. When we
   see this echoes, we know at which point the sequencer is. The gambas drum
   machine sends an echo for every quarter, and uses this information, when it
   comes back, to give a visual feedback. These echo events can contain some
   user data - that datum is used to distinguish them; in the program, this can
   reveal a start of a measure, and in that condition a new measure is loaded
   (buffered in advance) to the sequencer.

   The problem, in this program, is that the normal alsa interface does not
   provide a callback to signal when an event is ready to be read (even if it
   did, gambas2 could not use it) . This is simulated by the CAlsa class, which
   raises a Gambas event when an alsa event of type "echo" has been read, but
   the CAlsa class itself uses polling to interrogate alsa. The frequency of
   this polling can be varied using the slider "poll freq" in the main program.
   Setting  this  frequency to low values should show an imprecise visual
   feedback of the drum machine, but the precision of the music should be
   unaffected.
     _________________________________________________________________

   No backlinks to this page.

References

   Visible links
   1. file://localhost/tmp/index.html

   Hidden links:
   2. file://localhost/tmp/.html
   3. file://localhost/tmp/.html
-------------- next part --------------
Home
root
2010-08-23

%!postproc(html):  ~~(.*?)~~  <strike>\1</strike>

= Home =

= Home =
Created sabato 10 luglio 2010


How to interface Gambas to external libraries


== INTRODUCTION ==
There are lots of shared libraries available in a Linux system, capable of doing a lot of useful things, and many of those libraries can be used from Gambas using some of its features.


The first step to do is to find a suitable library to use for a given purpose; not all the libraries can be used, but the vast majority can. The prerequisite is that the library is written in plain C. Most libraries are written in C, while others are written in C++ and, perhaps, still other languages. This document will focus only about C libraries.


Once the intended library is found, its general logic must be understood in order to determine what is needed, and how this new stuff will be used by the final program. The full documentation of the library, and possibly some example, should be readed carefully. Libraries are not programs, and hence their philosophy is quite different. A program tends to contain only the subroutines required to do its job, while a library is what the name implies: a collection of subroutines, to be used many times, by many different programs. It is not uncommon to find, inside libraries, two or more subroutines which do the same thing, in a slightly different way. Libraries sometimes are written keeping in mind that they will be used by different languages, not only C: python, ruby, ocaml, many others, Gambas included. Libraries tend to encapsulate the details of a task inside a "handle", in a way similar to a Gambas object; but, they don't have properties and methods - everything is carried out by calling functions which these handles are passed to. If you think at a Gambas class with three properties and four methods, an external library implementing the same thing will have at least seven (three+four) subroutines, and probably a couple more to create and destroy the object. Other than this, there is not a big difference from setting a property and calling a function, the latter is simply a bit longer to write. To search for what we need, we must be prepared to read a lot of documentation, too often badly written (hey, apropos, how well do we document our software?).


== THE EXTERN DECLARATION ==
Extern declaration is simple. It is like a normal Gambas subroutine declaration, but preceded by "EXTERN". The EXTERN keyword says to gambas that the body of the procedure is not defined by the gambas program we are writing, but somewhere else (an external library). We must also specify which library to use: this is done by the clause "IN libraryXXX". Alternatively, you can use a separate LIBRARY statement: all the subsequent extern declarations will refer to this statement. Either "IN library" or "LIBRARY xxx" can specify a version number after a colon, and this is recommended.


Let's see an example, choosen because of its simplicity:



```
LIBRARY "libc:6"
EXTERN getgid() AS Integer
```

These two lines say that a function named "getgid" exists in the library "libc" version 6. This function takes no parameters, and returns an integer (the group ID).


The same thing can be written like this:



```
EXTERN getgid() AS Integer IN "libc:6"
```

Another example, slightly more complicated. This time we have parameters, and the last thing to say about the formal declaration:



```
' int kill(pid_t pid, int sig);
EXTERN killme(pid AS Integer, sig AS Integer) AS Integer IN "libc:6" EXEC "kill" 
```

The first line (the comment) shows the original declaration, and the second line the gambas one. We can note a number of things. First, what does the original declaration mean? It means "a function called kill returns an int, and it accepts two parameters. The first is named pid and its type is pid_t;  the second is named sig and its type is int". Contrary to Gambas, the C language puts the type of a variable before the variable instead of after.
Second, what in Gambas is called "Integer", in C is called "int". Third, what is "pid_t"? It's a type; we can understand it because it is written in a place where a type specifier is expected, and because ends with "_t" (underscore t).
Third, a new clause EXEC "kill" is used in the Gambas declaration. This is necessary because we want to use a function named kill, but KILL is a name already used by Gambas. So, in Gambas, we must name the function differently, but anyway we must indicate its true name inside the library. The declaration says "I declare an external function named killme, but its real name is kill". I chose the name killme because in the attached gambas example this function is used to kill the running program.


To be sincere I noticed that, even without renaming the function from kill to killme, the program was working the same. May be that this has something to do with case sensitivity - C is sensitive, and Gambas not, so there is still a difference between kill (lowercase) and KILL (uppercase). Anyway, when there is a possible name conflict, it is best to use this renaming technique.


== WARNING --- END OF THE EASY PART ==
//(just joking)//


At this point, several things must be noted. Most of the suitable libraries are written in C, which is a different language than Gambas. We will need to know at least a little of C declarations, in order to translate them to gambas. Referring to the last example, one could ask why I translated the pid_t type to integer. The simple and correct answer is "because on my system the pid_t type is actually an integer". This answer is really correct, but must be explained better, talking about agrumes (?). We can think about lemons and oranges, which both are agrumes, and are very similar: they weight more or less the same, and often can be interchanged; you can eat them directly, or squeeze them to drink their juice, but it is unlikely that you will put orange juice on your fried fish. In C, this is expressed by the fact that it is unlikely that you want to use the kill() function passing it an arbitrary integer. Surely, you will pass a process identifier (PID), which actually is an integer, but it is indicated more precisely as pid_t. In my motivation, I also said "on my system the pid_t type...". Yes, on my system - on most system, the type pid_t is an integer, but this could be different.


The final answer can be found by typing these two commands in a terminal:



```
grep -r pid_t /usr/include/* |grep "#define"
grep -r pid_t /usr/include/* |grep typedef
```

which will show the involuted way types are managed in C. This argument is way too complicated to go further; giving that Gambas runs on Linux, and presumably on desktop systems, we can consider that all the parameters passed to a function, and returned by it, will be either integer, or pointers - which are integer too, or strings - which are pointers that are integers. There can be also floating point numbers - float and double. The following table lists some of the types you can encounter in a C declaration, and the suitable type to be used in gambas:



```
C type			Gambas type
int			->	integer
long			->	long
float		->	single
double		->	float
xxxx*		->	pointer (the asterisk means exactly "pointer")
char*		->	pointer - but see later
other types	->	integer or pointer (depends on the declaration); see later
```

We will start to briefly introduce pointers, which are little used in Gambas. A pointer is an integer, but used very differently. The thing that more closely resembles a pointer in Gambas is a class instance. When you create, say, a Form in Gambas, a lot of data is stored somewhere in memory. That memory will hold all the specific settings of the form: its caption, its color, the list of all its children, and so on. The address of that block of memory is returned to your program, and stored into the variable which refers to the just created form:



```
MainForm = NEW Form()
```

That variable MainForm[  .t2t]is really a pointer: in only 4 (or 8) bytes it tracks a lot of data, stored somewhere in memory at a specific location (address). The memory is a long sequence of cells (bytes), each identified by a progressive number. A pointer contains the identifying number of a cell of memory (its address). In C, pointers are used for two reasons: the first is that passing only an address (a pointer contains an address), is much quicker than passing a lot of data; this is the same reason why Gambas instance variables like MainForm[  .t2t]are similar to pointers.


The second reason is when the called function should modify the variable we pass. For example, if we in Gambas wrote:



```
INPUT a	' where a is an integer
```

in C we would write:



```
void input(int *a);
...
input(&a);
```

The reason is that we want our INPUT command to fill our variable "a". In C, we must call input() and say to it where our variable is, in order to let it fill the variable. The ampersand "&" takes the address of the variable, and passes it to the function. The declaration of input() says "int *a", which states that "a" is not an integer, but a pointer to an integer, ie, the parameter says where to find the value, not the value itself.


== GAMBAS IMPLEMENTATION OF POINTERS ==
Gambas has the datatype Pointer, and a set of operations suitable for it. To use a pointer, a normal declaration is required like any other variable. Then, a value must be assigned to it. When using a normal variable, often you can assign a literal, for example you can write "a=3". With pointers, this is not advisable. A pointer gives access to any cell location in memory, but you should know in advance what location you are interested in, and that location must be the correct one for the intended purpose, otherwise Gambas or the operating system will get angry. This is much the same as saying that you can not write "MainForm = 3". You can write "MainForm = NEW Form()", or "MainForm = AnotherExistentForm", or "MainForm = NULL". So, a direct assignment to a pointer will always be to NULL, to another pointer, or to a call to some function returning a pointer. Just as a class instance variable like MainForm.


Sometimes an external function returns a pointer, and this pointer will be required in order to invoke subsequent calls to the external library. This case is much like creating a Form, and using its reference to operate on the form itself. In this case, the data behind (pointed by) the pointer is said to be "opaque": we can not see through something opaque, so we don't know, and we don't want to know. This is the simplest case; an example about this is the LDAP library. The first thing to do to interact with LDAP is to open a connection to a server. All the subsequent operations will be made on the connection created by a specific call. Things go like this:



```
LIBRARY "libldap:2"
PRIVATE EXTERN ldap_init(host AS String, port AS Integer) AS Pointer
PRIVATE ldapconn as Pointer
...
ldapconn = ldap_init(host, 389)
IF ldapconn = NULL THEN error.Raise("Can not connect to the ldap server")
```

As already seen, a LIBRARY is specified. Then, an EXTERN function is declared; this function is the one which must be called in order to do anything with ldap. The last two lines are the ones that, when executed, will open the connection and store its handle, or instance, for this connection. In this specific case, ldap_init() returns NULL if something goes wrong, so we can test for NULL to raise an error. Once obtained a handle to the connection, this handle must be specified on every subsequent call to the ldap library. For example, to delete an entry in the database, the following must be used:



```
PRIVATE EXTERN ldap_delete_s(ldconn AS Pointer, dn AS String) AS Integer
...
PUBLIC SUB remove(dn AS String) AS Integer
DIM res AS Integer
```


```
res = ldap_delete_s(ldapconn, dn)
```

Unfortunately, things are not always so simple. One of the reasons C uses a pointer, is to let the subroutine write some data in the location indicated by the calling parameters. Remaining in the initialization of a library, ALSA for example is different. To initiate a dialog with the alsa sequencer, a handle for the sequencer is needed. The C declaration for this function is:



```
int snd_seq_open(snd_seq_t **seqp, const char * name, Int streams, Int mode);
```

Hep! what is this "snd_seq_t **seqp"? We know that the asterisk is used to indicate a pointer - so what could mean a double asterisk? It's easy: a pointer to a pointer. This function snd_seq_open() uses the pointer notation to fill a value; this value is a pointer itself. Differently from the case of LDAP, where the function ldap_init() returns only one value, here this function returns two values. The return result of the function is an error code - all the ALSA functions use this scheme. A return result of zero means success. So to return more than a value, the function can only write some data to some location we specify, by using a pointer. The value it writes has type pointer, so the notation "double pointer" is used. So far so good. But can we translate this to Gambas? Yes and no. We need a pointer, and this is not a problem. Then we must take the address of this pointer, in order to obtain "a pointer to a pointer". Gambas3 can do that, Gambas2 can not.


Let see the simpler way, only available in Gambas3. The VarPtr() function returns the address of a variable or, in other words, a pointer to that variable - and its name says so: VAR-PTR, "variable pointer". In gambas3 we would write:



```
PRIVATE EXTERN snd_seq_open(Pseq AS Pointer, name AS String, streams AS Integer, mode AS Integer) AS Integer
...
PRIVATE AlsaHandler as Pointer
...
err = snd_seq_open(VarPtr(AlsaHandler), "default", 0, 0)
```

The EXTERN declaration says that snd_seq_open() expects a pointer, which is true: snd_seq_open() expects a pointer to a pointer, which is anyway a pointer. So we declare a variable Alsahandler as pointer, and pass its address using VarPtr() which returns a pointer to the variable.


In Gambas 2 this is not possible - we don't have VarPtr(). We must anyway declare a variable to hold the handle, like before, but then we can not get its address, or a pointer to it. We will attack the problem from another side. We need to find a location in memory to pass to alsa and, after that, go to peek in that location. In Gambas 2 the only way is the Alloc() function. By using Alloc(), we reserve a piece of memory somewhere, and obtain its address. This address is what we need to pass to snd_seq_open(): a pointer contains an address. Well, can we start to write something? Yes:



```
' int snd_seq_open(snd_seq_t **seqp, const char * name, Int streams, Int mode);
PRIVATE EXTERN snd_seq_open(Pseq AS Pointer, name AS String, streams AS Integer, mode AS Integer) AS Integereger
PRIVATE AlsaHandler as Pointer
...
DIM err AS Integer
DIM ret AS Pointer
```


```
ret = Alloc(4)	' 4 is the size of a pointer in 32-bit systems; 8 for 64-bit systems
err = snd_seq_open(ret, "default", 0, 0)
```

When we want to open the connection and obtain a handler, we reserve some memory and pass its address, in order to have snd_seq_open() write useful data there. But then, how can we read that location to retrieve the handler? Here come the functionality of pointers in gambas. Pointers can work like streams - you can read from them and write to them. Actually, the memory of the computer is a file of memory cells, right? We can read a value from a pointer with:



```
READ #ret, AlsaHandler
```

At this point, we succeeded. It's a little like an Odissey, but it is worth! We only must release the memory we reserved with Alloc(), so the Odissey is not yet over. That memory has been used in a temporary way, and we could neglect it, but if this operation was made many many times in a program, the program continues to eat memory. Normally Gambas has automatic memory management, but in this case it cannot help because it doesn't know what we are doing with the memory, so we are responsible to free the memory when we are done with it:



```
Free(ret)
```

There are other reasons to use a pointer. Take the declaration of getloadavg(), a nice function that tells us how much our CPU has been busy in the last minute:



```
int getloadavg(double loadavg[], int nelem);
```

This nice C language can even pass arrays to functions? Yes. And try to guess how it does it? Pointers again...
In this case, the array passed to the function will be filled with one or more values, each signifying a different kind of load average; each value will be put in consecutive locations in the array. But C is not smart enough to know how big an array is, so the function can not know how many values to write. We have to tell the function, through the "nelem" parameter. To make it short, the correct declaration for this situation is this:



```
EXTERN getloadavg(ploadavg AS Pointer, nelem AS Integer) AS Intege	r
```

We need to pass a pointer, because the function getloadavg expects a pointer, even if this could not be obvious by looking at its declaration. The pointer must point to free ram, because the function will fill the memory pointed to by this pointer. Then, we will read the values and, lastly, we will fre the memory. Ax example usage is:



```
PUBLIC SUB get_load() AS Float
  DIM p AS Pointer
  DIM r AS Float
```


```
p = Alloc(8)
IF getloadavg(p, 1) <> 1 THEN
  Free(p)
  RETURN -1   ' error
ENDIF 
```


```
READ #p, r
Free(p)
RETURN r
	END
```

The subroutine is straightforward: we allocate 8 bytes because a gambas float is 8 bytes long. Then we call the getloadavg(), which will fill these 8 bytes. Whether the operation succeeds or not, we must free the allocated memory. But, If the operation succeeded, first we must read the memory. This is why we have two "free(p)" in the subroutine. A more elegant way could be to use a FINALLY clause, but this way we are more close to the C spirit...


getloadavg() returns the number of values read. Asking for only one value, it is legitimate to interpret a return result different from one as an error. If we asked for three, and only obtained two, we would have had a strange situation - something in between from a correct result and a failure. This and other funny things can be seen when trying to use some historical interfaces. For example, in some version of Unix there is not a clear method to read a file name. The function returns the number of character written, but no indication that the name is shorter than that. So you are only sure to have read the full name when you passed a buffer longer than the function result. But you have the function result *after* the call, not before! The typical usage is to take an arbitrary value, say 256, and do the first try. If it fails, you add another 256 bytes, and try again. And so on...


Back to our getloadavg(), anyway. We used Alloc(8) because a gambas float is 8 bytes long. And we used a gambas float in order to interface with a C double. But where is stated that a C double is 8 bytes long? In fact, there are out there machines where a double is 10 bytes. This is a serious issue, because the above subroutine will not work. We couuld allocate more memory, perhaps 64 bytes instead of 8: I am pretty sure that no computer exists wich use more than 64 bytes for a floating point number. But anyway, trying to read a 8-bytes value out of a 64-bytes value would yeld a nonsense. Perhaps is better to let a program crash, instead of giving the impression that it works. One question could arise... how can a C program work on so many different architectures? The answer is the following: because a new, perhaps different architecture, must have an omogeneous set of kernel, include files and compiler. In a real C program you will never see a statement like "alloc(8)", but instead something like "alloc(sizeof(double))". The compiler knows the size of a double, and the keyword "sizeof" puts the knowledge of the compiler into the source program.


== More on pointers ==
Some better explanation is needed, at this point. The instruction READ #ret,... reads something from the location pointed to by the pointer "ret". It is important to stress once again that this kind of things must be designed carefully. Working badly with pointers is one of the most common cause of failure of C programs and, when using pointers, Gambas can do no differently. In this case is easy, because we made our job in a few lines in a row.


The semantics of the READ instruction in pointers resembles the one of STREAM, but with an important difference: while the stream is advanced automatically after a read or a write, the same operation on pointer does not. If our memory contained two variables to be read, one after the other, we had to advance the pointer by ourselves:



```
READ #mypointer, var1_4byte
mypointer += 4
READ #mypointer, var2_4byte
```

As you can see, it is possible to treat a pointer like an integer. Using this mechanism, one can walk forward and back in memory to emulate what in C are called "struct". A "C struct" is a group of heterogeneous variables put side by side, which then can be treated like a single variable. Its closest counterpart in Gambas is, again, a class. Structures are often referenced by a pointer, especially when they are to be passed to a function. We will se later an alternative method to implement this in gambas, but now we are talking about pointers, so we will finish this topic. The C language has also "unions", which are an unknown thing to Gambas, and therefore they must be emulated using pointers (not completely true). Unions are composed of two or more variables that share the same memory: writing to one variable modifies implicitly the others too: they are overlapped. The reason for this is to describe in a unique type different layouts. By combining struct and union, complex configurations can be generated, and this layouts are difficult not only to manage, but even to understand. To give an example, we will talk again about the ALSA sequencer. The sequencer works with events (mostly notes to be played) that have a time stamp to indicate "when" these events are to be played or carried out. This time stamp can be expressed in ticks, which is the traditional way related to the metronome. Ticks are normal integers. But ALSA goes further, and permits to use real-time time stamps, a much more precise indication, useful to synchronize music with other things (video, for example). This measurement is more precise, therefore it needs more memory to hold the bigger precision (two integers). So there are events having time specified with 4 bytes (an integer), and events having time specified with 8 bytes. They could have used simply two fields, respectively of 4 and 8 bytes, one after the other. But by using a union, they saved 4 bytes. The real memory reserved for time stamp is 8 byte, big enough to hold either of the two values, but at a logic level these two values are mutually exclusive. All this is handled automatically by the C compiler. When playing with unions in Gambas, we must do all this by ourself.


== A Gambas drum machine ==
A concrete example about all we have seen until now is the ALSA library: a simple, very basic drum machine will be implemented in Gambas. First of all, what is a drum machine? It is a machine which emulates the combination of a drum player and a drum set. Many musicians use it, especially those who produce music all by themself. In the Gambas world, this is accomplished by using ALSA. ALSA is the Advanced Linux Sound Architecture, and its aim is to offer a complete set of functions to produce sounds and, hence, music. From the point of view of a computer, generic sound and music are two different things. If you play an MP3 file, ALSA will move the speakers as directed by the MP3 data, without knowing or analyzing anything. We are interested in another kind of interface - the sequencer interface. A sequencer copes with "events", which are "played" at the right times, using suitable parameters (or properties) for the event. If we think at a piano player, we can see that he presses his keys, one or more together, at different moments. Simply, every keypress it's an event. The three most important things when pressing a key on a piano are: 1) when; 2) which key; 3) how strong. If you want to play two notes at the same time, you create two events having the same "timestamp". If you want to play a chord, you create three events having the same time, three different notes, and (probably) the same strongness. Then, you feed these events to the sequencer, and it will send them to something else which will produce the sounds. The sequencer does not care about producing sound: this can be produced by some software, or be outputted by a MIDI interface to some external musical instrument. If, instead of a piano, you say "I want trumpets", the three notes will be played by three trumpets. This interface does not specify "how long the notes play": they will sound until another event will say to stop. So a single note is actually done with two events: a NOTE-ON and a NOTE-OFF. In the case of drums, a note identifies a different piece of percussion: bass drum, snare drum, cymbals, maracas, bells, even whistles and much more.


Music and computers have much in common. For example, a typical musical measure is divided in 4 quarters. Is the number four uncommon in computers? Keys on piano are numbered, and the strongness ("velocity") of a keypress can be expressed by a number, as well as the duration of a note. There are other values involved, for example the force a flute player uses to blow in his instrument (after the note has been started), but we will not go so deep. Only let me say that a good sequencer, combined with good hardware, can simulate surprisingly well an entire orchestra.


The simple drum machine has a grid on the screen, and every cell of the grid represents a note: its row number specifies the note to be played (on a drum machine, different notes correspond to different pieces, or instruments). The column of a cell represents the time when the note will be played. The grid contains two measures which are played over and over - this is enough to construct a normal rythm. Every measure is divided in 4 quarters, and every quarter is divided further in four 16th's. The top row is a visual ruler, and the leftmost column is used to hear an instrument. Clicking in a cell toggles an "o" marker; to play the pattern click the button "Play grid". Other buttons produce some other sound, just to show simpler things like chords, legato's, arpeggio.


To have the program produce sounds, the correct client/device and port (alsa terminology) must be written in the first two lines of FMain.class, and depends on the hardware installed. Issuing an "aconnect -ol" in a terminal shows the suitable devices. If a software synthetizer is present, like Timidity, probably it will show as "client 128". The MIDI out device could be number 16. The port number can probably be always 0. Another way to find out the correct numbers is to use an already working sequencer or MIDI player, like Kmidi, and peek at its midi configuration.


The main reason to analyze this program is to look at a complete interface with an external library. Most of the issues have been presented already, but an important part not yet covered is how to cope with C structures using pointers.
Instead of using pointers, an alternative way is to use declare variables in a class, and then pass an instance of that class to an external function; this is not covered here: the method would be better and clearer, but the pointers are more versatile. A yet better approach is possible in gambas3, which has native structures.


Because the program is very alsa-specific, we will skip everything but the "event structure". Once all the things required by alsa are done (opening alsa, creating queues, ports, starting them and so on), only remains to construct events and send them to alsa, which will play them at the correct time. An event is defined by alsa like this:



```
snd_seq_event_type_t   type
unsigned char   flags
unsigned char   tag
unsigned char   queue
snd_seq_timestamp_t   time
snd_seq_addr_t   source
snd_seq_addr_t   dest
union {
   snd_seq_ev_note_t   note
   snd_seq_ev_ctrl_t   control
   snd_seq_ev_raw8_t   raw8
   snd_seq_ev_raw32_t   raw32
   snd_seq_ev_ext_t   ext
   snd_seq_ev_queue_control_t   queue
   snd_seq_timestamp_t   time
   snd_seq_addr_t   addr
   snd_seq_connect_t   connect
   snd_seq_result_t   result
} 
```

The first lines, before "union", are common to every kind of events; in fact, they contain the event "type", some "flags", a "tag", the "queue" where to enqueue the event, the "source" (who created the event?) and the "dest" (to whom send this event?). Let's look at the firt line: "snd_seq_event_type_t type". The field is named "type", and its type is "snd_seq_event_type_t type". So we must inspect the documentation to find out how this type is made. We find:



```
typedef unsigned char snd_seq_event_type_t
```

The line above says that "snd_seq_event_type_t" is an alias for "unsigned char". An unsigned char is a byte.
The next three fields in the struct are flags tag and queue, all of type unsigned char, hence byte.
Then the timestamp "time" is declared as "snd_seq_timestamp_t"; searching again for declaration, we find that it is a union containing either a midi tick (an unsigned int) or a struct which is composed by two unsigned int. The net result is that the length of this field is 2 unsigned ints, or 8 bytes on 32-bit systems. The first part of an event is composed, by our point of view, of the following fields:



```
type_of_event			a single byte
flags					a single byte
tag					a single byte
queue				a single byte
timestamp, composed of:
  tick (int) or tv_sec		an integer
  tv_nsec				an integer
```

If we want to fill the field "tick", we must point a pointer to the beginning of the event memory, then advance the pointer by 4 bytes, then write to the pointer the intended value (an integer).


The CAlsa class allocates memory for just an event:



```
PUBLIC SUB alsa_open(myname AS String)
  	  ...
  	  ...
  ' alloc an event to work with. It is global to avoid alloc/dealloc burden
  	  ev = Alloc(SIZE_OF_SEQEV)
```

and then manipulates this memory over and over before passing the event to alsa. The subroutine prepareev() clears the event and fills the common part. Here is its declaration:



```
PRIVATE SUB prepareev(type AS Byte, flags AS Byte, ts AS Integer) AS Pointer
  	DIM p AS Pointer
  	DIM i AS Integer
```

The parameters of the function reflect what we are interested in - for example, we are not interested in the "tag" field, so we don't pass a value for it. The first step is to clear the event, to make sure that no unwanted data is there from before:



```
' clear the event
p = ev
FOR i = 1 TO SIZE_OF_SEQEV
  WRITE #p, 0, 1
  INC p
NEXT 
```

The pointer "p" is pointed to the beginning of the event with "p = ev". With a for-next a stream of zeroes is written out. The instruction "WRITE #p, 0, 1" writes in the memory pointed by #p; it writes the value 0, using 1 byte. We must specify "1" (ie 1 byte), because the second parameter (0) is an integer constant, so gambas thinks it should write 4 bytes (or 8) - we force it to write a single byte. If instead of using a constant ("0"), we used a variable of type byte, gambas would have know the size of the variable (1 byte), and we could have avoided to specify the size. But beware! This works with variables, but not with constants (a gambas 2 bug, I suppose).


A better algorithm to clear the event would be to write 4 bytes at a time, and reduce the loop to 1/4. A still better way would be to simply rewrite fields we are interested in, and clear only the fields we know that are dirty.


After clearing the event, we start to fill the relevant fields. Again, we point our pointer "p" to the correct place (we moved it, remember?), write a value, and move the pointer afterward:



```
p = ev
WRITE #p, type
p += 1		' now p points to the flag field
```

Notice that the "WRITE #p" has a slightly different syntax. This time a variable is written, so there is no need to tell gambas how many bytes to write. We want to write a single byte, and the variable "type" is 1 byte long.


The rest of the routine is a repetition of what we have already seen. At the point of writing the timestamp, which is a C union, there is the following code:



```
WRITE #p, ts  ' timestamp
p += 4
```

This single instruction writes the first of the two integers of "snd_seq_timestamp_t   time". Then:



```
ts = 0
WRITE #p, ts  ' 2^ part (realtime event)
p += 4
```

Well, these three lines are not needed. We cleared all the memory before, so there is no need to set any field to zero. But we should anyway move the pointer. The two previous blocks of code could be as follows:



```
WRITE #p, ts  ' timestamp
p += 8
```

The subroutine prepareev() is called by noteon() and noteoff(), which then continue to fill the event with own data. The noteon() subroutine is this:



```
PUBLIC SUB noteon(ts AS Integer, channel AS Byte, note AS Byte, velocity AS Byte)
  DIM p AS Pointer
  DIM err AS Integer
```


```
p = prepareev(SND_SEQ_EVENT_NOTEON, 0, ts)
WRITE #p, channel
INC p
WRITE #p, note
INC p
WRITE #p, velocity
```


```
err = snd_seq_event_output_buffer(handle, ev)
```

The subroutine accepts a timestamp "ts", which says -when- to play the note, and refers to the time when the queue was started. A timestamp of zero, or anyway a value less than the current time of the queue, is played immediately. If the timestamp is greater than the current queue time, then the event will be played in the future, when the queue will reach the correct time. But alsa can also use relative timestamps, setting a flag in the event. The gambas CAlsa class uses this possibility embedding it in the timestamp directly. If the ts parameter to the prepareev() is set to a negative value, then the routine inverts its sign and sets the relative flag. In the main program, the routine btArpeggio_Click() produces three notes in succession by using this possibility:



```
PUBLIC SUB btArpeggio_Click()
  alsa.noteon(0, 0, 60, 100)
```


```
alsa.noteoff(-100, 0, 60, 100)
alsa.noteon(-100, 0, 64, 100)
```


```
alsa.noteoff(-200, 0, 64, 100)
alsa.noteon(-200, 0, 67, 100)
```


```
alsa.noteoff(-300, 0, 67, 100)
```


```
alsa.flush
	END
```

To subroutine starts the first note at time 0, which means "now". After 100 ticks, the note is shut off, and a new one is started (timestamp=-100 means "100 ticks after 0 -- 100 ticks after now"). And so on for the next notes.


Given that playing notes is the most common task, and that to play a note one must create a note-on and a note-off, the CAlsa class implements a routine to do that with a single call. The following routine:



```
' 32 notes having their duration less than their spacing
' "staccato": every note terminates well before the next begins
PUBLIC SUB btStaccato_Click()
  DIM i AS Integer
```


```
FOR i = 1 TO 32
  ' relative timestamp=10, 20, 30... successive steps by 10
  ' but the notes have duration=5, not 10
  alsa.playnote(-10 * i, 0, 60 + i, 100, 5)
NEXT
alsa.flush
	END
```

uses a single call to playnote(), which in turn generates internally two events.


Lastly, to be precise, here is an explanation of the drum machine algorithm.


The whole thing is to produce a stream of events, which will be played later. We must prepare a bunch of events in advance, so the hardware has data to work on. But a drum machine can be kept on for a long time, and we can not buffer all the needed events - we must supply a certain number of events ahead of time, not too much and not too little. The internal "pointer" of the drum machine always is a musical measure ahead of the sound we are hearing. We can not predict reliably "when" new data will be needed, because the sequencer consumes the events basing itself on a timer that can be different from our. Without a feedback from the sequencer, it is nearly impossible to keep in sync. The problem is even more difficult if we want to have a visual feedback on what the sequencer is playing in a given moment. This is solved by adding, in the event stream, some event whose purpose is not to make sounds, but to come back to us: an echo. The sequencer receives this added events, and sends them back to us at the correct moment. When we see this echoes, we know at which point the sequencer is. The gambas drum machine sends an echo for every quarter, and uses this information, when it comes back, to give a visual feedback. These echo events can contain some user data - that datum is used to distinguish them; in the program, this can reveal a start of a measure, and in that condition a new measure is loaded (buffered in advance) to the sequencer.


The problem, in this program, is that the normal alsa interface does not provide a callback to signal when an event is ready to be read (even if it did, gambas2 could not use it) . This is simulated by the CAlsa class, which raises a Gambas event when an alsa event of type "echo" has been read, but the CAlsa class itself uses polling to interrogate alsa. The frequency of this polling can be varied using the slider "poll freq" in the main program. Setting this frequency to low values should show an imprecise visual feedback of the drum machine, but the precision of the music should be unaffected.






More information about the User mailing list