|
|
|
system
architecture |
|
|
|
chapter
three |
|
For all software applications, the right choice of destination platform
and host system is paramount to the development of a program. From
the outset of the project we have placed an emphasis on the universal
compatibility and portability of MIVI. Although implementation will
be limited to the Windows operating system, we will attempt to make
any future porting of our efforts, to other OS’s, as painless as
possible. In the next few pages, we will discuss the why, which
and how of such future proofing measures. For a summary of the system
architecture and dataflow, see figure 4.1 at the beginning of the
next chapter.
The choice of MIDI, itself, needs little justification – as a standard
for musical IO, it is the only one implemented in most OS’s by default.
Its presence, however, in the application programming interfaces
(API’s) of Windows, MacOS, UNIX, Linux and many other systems, while
assisting us, will not automatically make MIVI as platform independent.
This is because MIDI dictates no single method of implementation
– not only will the audio hardware differ from device to device,
but so do the software interfaces to it: the kernel support and
multimedia subsystems like, for example, Microsoft’s DirectX. Of
course, MIVI is not the first application to seek compatibility
across OS’s.
|
the development
of music software
|
|
When technology first entered the musical sphere, its influence
was limited to embedded systems. Composing, recording and mastering
were all handled by dedicated music hardware and, indeed, many stages
still lie under the purview of such devices today. However, as personal
computers (PC’s) become more capable and user-friendly, the industry
is gradually adopting them. A decade ago, this latter constraint
gave Apple Macintosh the upper hand, and hardware and software manufacturers
began to design for the MacOS platform.
In recent years, the IBM-compatible PC’s success in the consumer
market, due to its improved user-friendliness, flexibility and processing
power, has convinced many of these manufacturers to include support
for Microsoft’s Windows operating system as well. Even more recently,
as the PC OS market becomes more competitive, versions of the industry-standard
UNIX are demanding attention, featuring heightened accessibility
and multimedia-provision, whilst still exploiting the same dependability
and superior performance, as is seemingly the case with LINUX and
BeOS.
Established software developers are porting their audio editors,
sequencers and other music utilities to these systems. One of these,
Steinberg Cubase VST, merits our attention.
|
|
|
3.1
the VST host system |
|
|
|
Starting
out as a simple MIDI sequencer, Steinberg’s Cubase gradually acquired
competences in audio recording, audio processing and music scoring.
In its present form, it has the capability to host its own software
synthesisers and software effects processors as plugins to the parent
– or host – program.
This is the point at which Cubase acquired its VST suffix. Standing
for Virtual Studio Technology [46], this initially involved the
host application placing waveform data in a buffer and passing it
to a user-specified child process – the effect plugin – that would
modify (process) the data in the buffer and return execution to
the host. As the host’s song plays, such information is streamed
to the plugin and the buffer is outputted to hardware, post-modification.
The widespread popularity and adoption of this architecture prompted
improvements in the VST specification to allow these audio plugins
to respond to, and process, MIDI input. Upon request, the host now
places MIDI data in a buffer, which is passed to the plugin as well.
The combination of these buffers allows for many different applications,
as illustrated in figure 3.1.
As is visible in the diagram, MIVI represents a departure from conventional
plugin architecture and data flow. Indeed, the author can find no
evidence of the VST system being previously used in this way – with
no direct and immediate influence on the music being played back.
Indeed, when we get on to coding, in the section 3.1.3, the reader
will notice the proliferation of the word ‘effect’ in programming
keywords, such as AudioEffectX
and AEffect. Such
keywords stem from the early days of VST plugins when the only possible
applications were audio effects. Unfortunately, their continued,
and sometimes counter-intuitive, use is required to maintain backwards
compatibility.
|
fig.3.1 - VST data
flow
and applications
|
|
|
|
|
Outside of the VST scope, such passive graphical applications have
also been implemented in a plugin environment called WinAMP9,
by NullSoft. Oriented for the entertainment arena, WinAMP processes
musical input, using it to produce aesthetic graphical patterns,
called ‘Visualizations’. Unfortunately, many of the problems that
befall the MIDAS system (discussed in section 3.1.1), such as insufficient
graphics, midi and developer support, are present in WinAMP and
thus make it unsuitable in our project.
|
platform
independence
|
|
By coding MIVI for execution through VST, we are delegating issues
of MIDI IO compatibility, and other communication with hardware,
to the host’s manufacturer. Furthermore, in addition to encouraging
the involvement of third-party firms in plugin development, Steinberg
decided to license the VST hosting technology for general use. Thus,
a VST plugin is now not confined to the Cubase application, but
executable in any VST host, like Steinberg’s Nuendo or Emagic’s
Logic Audio applications. In all, VST hosts now allow for the execution
of VST plugins in MacOS, OSX, Windows, Silicon Graphics (SGI/MOTIF)
and BeOS operating systems.
|
musical
representation
|
|
Furthermore, since MIVI’s graphics are synchronised with all the
other events in VST, we can make use of the VST host’s native display
formats. In Cubase, this most notably includes the musical score
format, but also a drum score and piano roll (pianola) format. These
views can be easily set-up to display in the background, so that,
in one scenario, we can have the MIVI plugin display the physical
configuration of the instrument while Cubase explicitly indicates
the corresponding notes on the score, throughout playback. Thus,
the user can attempt to play the music from the score, only resorting
to MIVI when they reach an impasse. Outside of playback, the user
can employ Cubase’s sounding tool to click notes in the stave itself.
Ordinarily this would send the note to a synthesizer device for
auralisation, but in MIVI’s case, the user will be shown how to
play the note, as it relates to the real instrument instead.
|
accessibility
|
|
Although commercial concerns are largely irrelevant at this early
stage, one consequence of this decision is worth mentioning. Music
software, such as Cubase, is often targeted, by way of features
and pricing, at the professional market. This may beg questions
about the logic of designing an educational utility for a post-education
audience and the morality of restricting use to only the affluent
minority of society – a contradiction to the intended audience that
we identified in section 1.2.
In response, it should be noted that, while VST hosts, designed
and priced for the home market – including a ‘lite’ version of Cubase
(called Cubasis) – do exist, MIVI is also envisioned in a classroom
environment. Already, many music departments have invested in music
workstations, including a copy of such software – often the professional
versions. These are viewed as sufficient and necessary justifications
for the choice but, if for some reason the availability were not
enough, a dedicated, yet simple, VST host could be constructed to
allow the plugin to be universally executable. However, any such
host would lack the advantages of the score format, unless explicitly
provided.
|
|
|
3.1.1
alternative platforms
|
|
|
For many
software projects, the natural choice is to start from scratch,
coding the program as an independent (standalone) application. If
a hosted environment, such as VST, is available, programmers, often
with reason, cite problems regarding the opaqueness of operations
– the inability to see and control what is happening behind the
scenes – and thus choose not to use these platforms, despite the
advantages and shortcuts they might offer, so as to engender complete
faith in their implementation.
For two reasons, we conclude that VST, while omitting our need to
code for low-level hardware communication and providing us with
added functionality (such as the score and interface), also satisfies
these more stringent requirements.
Firstly, both VST and Cubase are benchmarks of professional standards
in music technology. In the studio environment, for which both VST
and its plugins are designed, it is necessary to have meticulous
timing – the host functions, and is written, in much the same way
as a Real-Time or Safety-Critical System [6], requiring the same
compliance from hosted plugins.
Secondly, without discouraging or hindering development of such
software, Steinberg regulates the access and existence of developer
resources. All developers must register with Steinberg, before receiving
the Software Development Kit (SDK), which contains the official
documentation to the VST system [46]. This, combined with the Steinberg-run
and -moderated Mailing List, form the only practicable resources
for VST programmers. In so doing, Steinberg minimises the potential
for misinformation, inherent in distance learning, while providing
an unbounded source of information.
Another system in development, here at the University of York, called
MIDAS10,
shares a similar rationale to MIVI – the visualisation of audio.
MIDAS is a programming language for the visualisation of music and,
in this capacity, represents an implicit specification for applications
to that end.
Although literature is scarce on the subject (due to its infancy),
several themes of the research compliment our own. Firstly, MIDAS
harnesses the power of OpenGL 3D graphics – although only a select
amount of OpenGL operations are currently promoted as keywords in
the MIDAS language. Secondly, MIDAS is designed to be platform independent
and, similar to many of the technologies employed in MIVI, should
compile on any platform with a C compiler.
Unfortunately, several aspects of the MIDAS system make it unsuitable
as a host for our project. As yet, no MIDI support has been stated
or documented – the language is designed to react to waveform audio
only. Furthermore, in this capacity, the graphics facilities available
are designed to enable programmers to produce highly abstract, algorithmic
graphical patterns, as opposed to virtual objects. Lastly, as with
standalone applications (discussed earlier), MIDAS has no default
support for the score or other musical notations.
When it reaches maturity, and with the addition of MIDI to the feature
set, MIDAS could represent an attractive option as a foundation
for projects like MIVI. Although the lack of existing literature
makes a review of the technology difficult, at this stage, Dr. Hunt
– the project’s leader – brings his experience and knowledge to
bear on this endeavour more directly in section 6.1.1.
|
|
|
3.1.2
programming language
|
|
|
By selecting
the VST plugin architecture, we have pre-determined the programming
language for our application – C1.
As universal as the other standards employed in our project, C has
been an industry-standard high-level programming language for many
years now. Hence, many argue that it has begun to show signs of
age and, for that reason, we take the liberty, in our Windows implementation,
of programming in C++ – an enhancement introducing, among other
improvements, an object-oriented programming (OOP) environment.
In addition to adding efficiency to our code, OOP will also present
the reader with a much clearer picture of what is going on in MIVI.
|
|
|
3.1.3
initialising the VST architecture
|
|
|
In
Windows, VST plugins take the form of Dynamic Link Libraries (DLL’s).
A DLL is a library of code, comprising functions and subroutines,
accessible to any program which ‘links’ to the .dll
file. Upon start-up, a VST host checks a specified directory for
such files, extracting a plain word title for the plugin and presenting
it as such within the parent application. Execution of the code
contained within the DLL is then suspended until the user activates
the plugin within the host.
|
plugin creation
|
|
When such an event occurs, the code at the DLL’s entry point (its
*main function – code ref.
15) checks that the parent is capable of hosting our plugin and
that sufficient memory is available, then creates a new plugin object,
effect. If all is successful
the function returns, to the host, a pointer to the new instance
of the plugin. Otherwise, an exception is raised, in the form of
a null return, whereupon the host reports the problem to the user.
In creating the plugin object, the object’s constructor (code ref.
18) is called. A constructor, as the name suggests, consists of
code to initialise the object, instantiate class variables and handle
any calling arguments. In the VST environment, we must tell the
host about the competences of the plugin, such as its audio IO capabilities.
From the information we provide, the host can work out which resources
the plugin requires, and thus save time by omitting unneeded callbacks
and data flow.
For MIVI, we set the number of audio inputs and outputs to 0
and let the host know that we want to appear as a VST Instrument
(a synth) as opposed to a VST Effect, thus saving the host from
having to supply audio data during playback. It will also allow
the user to intuitively select MIVI as a MIDI output device in the
host application.
|
issues with the
MIVI output device
|
|
It is worth noting that, by simply switching the track’s output
to MIVI, the track will not be auralised through a normal audio
device, and will be effectively muted instead. As yet, it is not
possible for the host to send notes to MIVI, then for MIVI to pass
them directly to a particular MIDI device. The SendVstEventsToHost
function allows the plugin to send MIDI messages back to the host,
but they will be treated as standard input and sent to the output
of the presently selected track, which only serves to complicate
matters.
So, to maintain the audio output, it is necessary to split the track’s
output in two, from within the host, and divert one to MIVI and
the other to the desired hardware device. Of course, another, less
tidy, approach is to simply copy the original track and select MIVI
for output on the copied track.
Alternatively, we can use this deficiency to our advantage. By muting
the track and sending it to be displayed through MIVI, we are not
muting other parts of the music and are allowing the user to superimpose
their own input. A range of learning tools called Music Minus One12
already employs this concept. Consisting of the score for a concerto,
or other such piece, and an Audio-CD of the music without the solo
instrument, the idea of the Music Minus One pack is to provide the
consumer with a backing to their solo performance. This has the
great advantage that the aspirant soloist can practise with a full
orchestra in the privacy of their own room. A similar process also
underpins Karaoke.
Other lower priority capabilities are requisitioned when the plugin’s
initialisation is complete through the plugins obligatory canDo
function (code ref. 21). The host calls this function for each capability
and it is up to the plugin to return true (1) for those it supports.
In the case of MIVI, we state support for receiving events – in
particular, MIDI events – and timing information (including playback
status). The host, therefore, now knows to provide such data.
Non-essential properties, such as vendor information and copyrights,
are also solicited in a similar away, but in MIVI, have been largely
ignored.
One chore, of note, is the unique identification of MIVI to the
host. To ensure that two plugins do not conflict when used together,
a plugin supplies the host a four digit alpha-numeric key to identify
them (code ref. 22). To prevent plugins using the same key, the
developer must register their key with Steinberg before use in the
public sector. Since MIVI will not be used in a commercial environment
and since all the keys of plugins, available to run in tandem with
MIVI, are known to be distinct, the "MIVI" ID, that we
shall use, need not be registered with Steinberg.
At runtime, VST will not activate the plugin by default – the user
must activate it manually. This involves opening the VST Instruments
window (because we have defined MIVI as a synth, rather than an
effect) and selecting ‘MIVI’ from the list of available plugins.
Then, by toggling the ‘power’ button on the VST Instruments console,
we make MIVI available, as an output device, to the open MIDI sequence.
Now, the user only has to go to the track they wish to have displayed
and select ‘MIVI’ as the output device, thus diverting all the MIDI
messages to the MIVI plugin.
|
|
|
3.2
the graphics environment |
|
|
|
In the
introduction, we mentioned our selection of graphics platform –
OpenGL. OpenGL has been chosen, not only for its graphical capabilities
and suitability to our application, but for its cross-platform portability
and programming language independence. As detailed in Appendix B,
OpenGL is essentially permissible in any C-based programming environment
and, as such, represents an ideal choice in trying to maintain platform
independence.
The reader is referred to the introduction to OpenGL in Appendix
B for explanations of concepts and terminology, used extensively
in this section.
|
|
|
3.2.1
integrating VST and OpenGL
|
|
|
As required
by other external dependencies, like the VST host and architecture,
our employment of the OpenGL graphics environment requires us to
initialise variables, devices and windows, and declare support for
event callbacks, like display, keyboard and input handlers, before
we can effectively start building the 3D objects themselves.
|
uniting the VST and
OpenGL environments
|
|
To streamline the design process, we shall employ the functions
of the GLUT utility toolkit library, as discussed in Appendix B.
However, the most common use for OpenGL and GLUT are in standalone
dedicated graphics applications, and it will be necessary to modify
the GLUT library to make it compatible with the VST host environment.
Primarily, we want to give priority to the VST host system, so that
music playback is not impaired due to costly graphic operations.
In a normal GLUT graphics application, the environment is initialised
and pointers to procedures that will handle the creation of 3D objects
and the effects of keyboard input are passed to GLUT. Following
this step, control of the application’s execution thread is transferred
to GLUT, using glutMainLoop().
In the GLUT library, this procedure consists of an infinite loop,
which, at each iteration, checks to see if the execution of the
code to update the on-screen graphics is required (requested when
the program calls glutPostRedisplay())
and checks the operating system’s message queues for pending input,
executing the appropriate user-defined input-handling function.
This repeats until the application, and hence loop, is aggressively
terminated either by the user, through the OS, or by the program
itself, through the calling of glDestroyWindow(),
during one of the user-defined callback functions.
Unfortunately, if we were to simply use this method in our VST plugin,
upon activation, control would be transferred to GLUT, from which
it would not return. In practice, since many of Cubase’s operations
are driven by interrupts, issued by the OS, some functionality endures
– for example, services reliant on high-priority interrupts, such
as that of the real time clock (RTC) – which regulates playback
– and the keyboard. However, features of equal importance, riding
on lower-priority interrupts, are blocked, including mouse events.
We therefore need to modify the GLUT library’s source code to accommodate
our situation.
|
distribution
of glut32.dll
|
|
Such addenda will alter the behaviour of the library. Not only does
this mean that the modified glut32.dll
file must be distributed with MIVI for it to function properly,
but that the file must not conflict with other versions already
in existence on the destination system. To overwrite an existing
file, with one that performs differently, will result in abnormal
and unpredictable behaviour in other applications that rely on the
original, un-doctored code. Thus, to be safe, we should make the
new library backwards compatible with the old. Note, however, that
this will not solve problems presented if the library with our modifications
is subsequently overwritten with a newer version of the standard
GLUT library without them.
|
platform
dependence
|
|
It should also be noted, that the modifications we are about to
make to GLUT, will largely be to the Microsoft Windows 32-bit (Win32)
code and, therefore, will not overcome similar problems faced on
other GLUT or VST host platforms. Such extensions to the modifications
are left the concern of future research, desiring to port MIVI to
these platforms.
|
modifying GLUT -
maintaining control
|
|
To solve the problem mentioned earlier, MIVI must retain principal
control of the execution thread in the VST environment. This involves
digging into the GLUT source code and finding some way to avoid
the infinite loop, in the file glut_event.c,
so that calling glutMainLoop()
will draw the screen, check the message queues, calling any appropriate
callback functions, and then return. It would then be up to our
plugin to call this function regularly enough to produce efficient
and continuously updated OpenGL graphics.
void glutMainLoop(void) {
-- safe guards
for() {
if(update_requested)
redraw(graphics);
if(input_received)
call input_handler_func;
}
}
fig. 3.2 (a) – Original GLUT
code
|
void glutMainLoop(void) {
-- safe guards
for()
glutMainLoopUpdate();
}
void glutMainLoopUpdate(void) {
if(update_requested)
redraw(graphics);
if(input_received)
call input_handler_func;
}
fig. 3.2 (b) – Modified GLUT
code
|
For us, commenting out the for
loop statement would be sufficient, but to maintain backward compatibility,
we move the body of the loop to a new function, glutMainLoopUpdate(),
and also provide a call to this new function in the original loop,
as illustrated in figure 3.2 (a) and (b). Now, other applications
can continue to call glutMainLoop(),
and receive the same functionality. We, on the other hand, can place
a direct call to glutMainLoopUpdate()
in the MIVIEditor::idle()
function (code ref. 23). This idle function is called by the VST
host, whenever both MIVI’s Edit Window is open (and, thus, as will
our OpenGL window), and the host is unengaged in other, more critical,
activities. In practice, this proves to be quite regular and frequent,
and is more than adequate for our purposes.
It is worth noting that the idle()
function can be called before the OpenGL window is fully initialised
which leads to problems when certain OpenGL functions are called.
Thus, following successful initialisation of the OpenGL environment
(code ref. 26), we set a Boolean flag, GL_ACTIVE,
to true. In idle() and
at other appropriate points in our program, we use this flag to
guard against premature calls to such functions.
|
modifying GLUT -
prioritising VST
|
|
GLUT’s conventional standalone nature means that it is also not
designed to create an OpenGL graphics window as a sub-window of
another non-OpenGL application. On the other hand, GLUT is perfectly
au fait with creating OpenGL windows subordinate to a parent
OpenGL window. In this instance, the program need only call glutCreateSubWindow()
and supply an integer, identifying the parent window, as returned
by the original call to glutCreateWindow()
to create the OpenGL parent itself. Deeper into the library, these
two functions simply call a generic window creation function, __glutCreateWindow(),
which optionally takes the integer, identifying the parent, as a
parameter. If non-zero, the function extracts this parent’s window
context handle (HWND) and
assigns the new child window to it.
Thus, it is a fairly simple process to modify the existing code
to allow us to simply provide the HWND
handle of the VST host application’s window (given as the *ptr
pointer in the MIVIEditor::open()
procedure – code ref. 24) for use as the parent. The code is this
time located in the GLUT source code file glut_win.c.
Once again, we should be wary about directly editing the code. We
would need to change the calling arguments of procedures glutCreateSubWindow()
and __glutCreateWindow(),
so that they can take an HWND
instead of an int, but
doing so will interfere with the inner workings of the library.
Instead, we create a new function, glutCreateMiviWindow(),
which will mimic the actions of the original two GLUT procedures,
using the HWND directly
in the Microsoft Foundation Classes (MFC) call to create the window,
as illustrated in figure 3.3 (a) and (b). Note, however, that we
yet retain the functionality of the glWindow
integer window identifier, so that we can pass it to the original
glDestroyWindow() function
at closing.
int __glutCreateWindow(int
parent)
{
-- initialise window style
MFCWindow(getHWND(parent), ...);
return (int)new_window;
}
int glutCreateSubWindow(int
parent)
{
int child;
child=__glutCreateWindow(parent);
return child;
}
fig. 3.3 (a) – Original GLUT
code
|
int glutCreateMiviWindow(HWND app)
{
-- initialise window style
MFCWindow(app, ...);
return (int)new_window;
}
fig. 3.3 (b) – Appended GLUT
code
|
|
preventing harmful
OpenGL destruction
|
|
Another modification to the original procedure is warranted, as
we want the window to omit the close button, which allows the independent
destruction of the OpenGL window. Were the window to be arbitrarily
destroyed, havoc would follow when MIVI, ignorant of its destruction13,
next tried to execute an OpenGL command.
Ideally, we want this control to rest with the plugin itself, to
unite the construction and destruction of OpenGL and the plugin
editor. So, we simply withdraw the close button from the window’s
title bar, by changing the window style mode, defined in glutCreateMiviWindow(),
from WS_POPUPWINDOW
(which is a combination of the flags, WS_POPUP,
WS_BORDER
and WS_SYSMENU)
to just WS_CAPTION
(which gives us a title bar), WS_EX_TOPMOST
(which prevents the window from being hidden), WS_THICKFRAME
(to allow manual resizing of the window) and WS_POPUP
(to make our window a popup window). Now, we have a window, which
the user can position and size to their liking, so that it is not
too intrusive with regard to other screen real estate, yet, at the
same time, cannot be obscured. Conversely, the further addition
of the WS_MAXIMIZEBOX
style allows the user to make MIVI the centre of attention, covering
the entire desktop, if desired. At the same time, the omission of
WS_SYSMENU
prevents the appearance of the close control.
|
containing the impact
of GLUT modifications
|
|
Instead of concentrating on backwards compatibility, we could take
advantage of the OS’s path-searching behaviour. If MIVI demands
the existence of glut32.dll,
we can prevent it from using the system’s generic version by placing
a copy of our own in the VST host’s home directory, which the system
will check before widening its search. Since MIVI is the only known
OpenGL-wielding VST code to date, possible version conflicts will
be minimised, thus devaluing any attempts to maintain GLUT compatibility
with other applications. However, this approach, (1) relies on the
absence of other versions of GLUT in the host’s directory, (2) requires
a copy of our library in the directories of every VST host on the
system, and (3) relies on a particular path-searching behaviour,
that could vary between OS’s. Hence, we favour our original approach.
|
manual inheritance
of GLUT functionality
|
|
However, given that the original GLUT window-creation functions
are now depreciated in the eyes of MIVI, another approach emerges.
As mentioned, GLUT is essentially a macro library of individual
GL procedure calls. Simply moving the necessary GLUT macros inline
with MIVI’s dynamic link library, mivi.dll,
will omit the need to package the GLUT library altogether, while
ensuring that all the modifications and functionality are still
available. Unfortunately, realising this is somewhat more involved
than simply copying the desired procedure code into MIVI (requiring
the identification and duplication of dependent GLUT private functions,
like __glutCreateWindow())
and this is, therefore, a step we leave to future work. The percentage
of GLUT which MIVI employs, is not immediately apparent from just
counting the number of distinct procedures called in the MIVI code,
and such further study might even show that the amount of code that
it would be necessary to migrate would cast doubt over the net advantage
and overall practicality of such a move.
|
performance
considerations
|
|
The performance bottleneck of our system will be the frequency of
idle() calls (code ref.
23) from the VST host. This can be observed by placing MIVI in SPIN_MODE,
(where the model is simply rotated on all axes by a small amount
each call) then clicking and holding down a button on the plugin’s
interface toolbar. In doing so, focus is temporarily usurped by
the plugin and rendering performance is dramatically increased.
The only net effect, however, is on the frame rate – the
frequency of scene refresh. The actual time available for scene
rendering, per idle() call,
is still enough, in normal operation, for complex scenes and graphics
operations.
Furthermore, when a plugin requests MIDI data, the VST host is obliged
to provide it with calls frequent enough to enable its real-time
processing, and thus, in combination with double buffering (described
in section 4.1.3), we are assured of acceptable scene fluidity.
The relative scarcity of idle calls is mainly due to the number
of operations the VST host must itself perform, before idle time
is available. Regardless of the absence of noticeable performance
increases, cheaper and more efficient approaches have been employed,
where available, in order to prevent extra loads on the host and
improve performance if, at some stage, it can bestow more processing
time on the plugin. Sometimes, though, when a lot of the host’s
work is involved with digital signal processing (DSP), as is often
the case with modern audio sequencers, this frequency will become
too sporadic. However, this problem concerns all VST plugins, and
is only effectively addressable in the host.
Some plugins are known to compound the problem. Although there is
a large movement, in the music industry, from hardware to software,
processor-intensive programs, such as DSP, nearly always execute
slower, resulting in longer latencies between input and output.
Because the two methods are often used together, the performance
advantages of hardware are usually compromised when the VST host
is obliged to delay its response in order to synchronise outputs
with software components. The net result is that there is a delay
of up to several milliseconds before audio events are heard.
For our application, it is vital to maintain synchronisation with
graphics, such as highlighted notes on the score, which are not
similarly delayed. Therefore, such delays must be annulled, thus
forbidding MIVI’s co-existence with such resource hungry DSP effect
plugins. Intuitively, the simplest and most practical solution would
be to disable audio processing in the host. This might also remove
a small overhead in the system and improve overall performance.
However, this seems to have the effect of disabling the entire VST
plugin architecture as well. Instead, we leave the audio enabled
and merely edit the imposed delay manually, by reducing the host’s
System Preroll from 500 to 0. In actuality, most people’s
hearing is not sensitive enough to require a zero delay, and a slightly
higher setting might allow resource-efficient plugins to execute
within time without noticeably compromising the synchronisation.
It should be noted, however, that DSP plugins are of principal use
in composition environments, not everyday MIDI-file playback.
|
other known issues
|
|
One further deficiency in the usage of OpenGL with VST is worth
mentioning. When the user closes the plugin, the host promptly ceases
execution of any thread in the plugin. In most circumstances, this
takes place without complaint. However, on the rare occasion when
the plugin is caught making an external call to a GLUT library procedure,
notably glutMainLoopUpdate(),
such abrupt execution produces errors. Usually, the result is merely
an error message from the host but, from time to time, it becomes
necessary to restart the host following this error, before the plugin
can itself be restarted. This could present another advantage of
bringing the GLUT functions in house, where their calls could be
more closely guarded. However, these functions proceed to call the
core GL library’s procedures, and it remains to be seen if the problem
wouldn’t simply propagate to the next layer of abstraction.
|
|
|
3.2.2
initialising OpenGL
|
|
|
The remaining
tasks involved with setting up the OpenGL environment (code ref.
26) largely follow convention. Using glutInitDisplayMode(),
we erect a display mode which uses double-buffering, red-green-blue
(RGB) colour channels and z-buffering.
|
|
|
Double-buffering simply allows the next frame’s graphics to be drawn
in a second buffer, while the contents of the first buffer are displayed,
thus minimising (often halving) the time taken to render each frame.
To take advantage of this, all we have to do is tell GLUT that we
support double-buffering (GL_DOUBLE)
and call glutSwapBuffers()
after each frame is ready for display.
Colours for computer screens use an RGB palette, with each colour
defined by respective amounts of red, green and blue. These components
are then mixed in a similar way to light rays of corresponding wavelengths.
OpenGL actually supports an extra dimension of ‘colour’ – the alpha
channel (GL_RGBA) – corresponding
to degrees of transparency. When combined with alpha-blending (GL_ALPHA),
it simply blends a foreground object’s colour with that of the object(s)
behind it, to an extent defined in the program. As we will soon
see, such operations are, unfortunately, not available to us in
our current implementation.
Z-buffering refers to the third axis in Cartesian co-ordinates –
that of depth. Previously, 3D scenes containing multiple objects
had to be rendered from the back to the front. Otherwise, distant
objects would appear on top of closer ones, contrary to reality.
Were the object to be rotated 180° around the vertical, what was
previously must be rendered in reverse order. The intervening angles
additionally present even more of an ordering challenge.
By employing a z-buffer, the depth of each object is stored (ie.
buffered) in memory, so that we can request the frequent calling
of GL_DEPTH_TEST, which
will automatically work out the correct rendering order to maintain
consistency. Unfortunately, this OpenGL function prohibits us from
using elaborate quality improvements such as anti-aliasing.
Aliasing is the resulting artefact produced when a diagonal line
is drawn on a bitmapped display – the line will appear jagged because
of the square nature of the pixels. Anti-aliasing [3] uses alpha-channel
blending to smooth these harsh edges with the background, presenting
the appearance of a finer line. Such features are unavailable to
us due to the order of OpenGL’s rendering pipeline, resulting in
conflicts between the GL_DEPTH_TEST
and GL_BLEND operations.
|
improvements
offered by alpha
blending and sorting
|
|
Were we to have more time, it would be possible to modify our code
to implement our own dynamic depth-sorting function, which would
nullify the need for GL_DEPTH_TEST
and enable the use of GL_BLEND.
This can be achieved by storing 3D objects in our own buffer, which
we manually re-sort, using an efficient sort algorithm such as QuickSort
when the scene changes, before sending them to OpenGL – a process
known as alpha-sorting. As one might expect, the lack of blending
capabilities also prevents other uses of partial transparencies.
Lighting is set up at initialisation, in the function glInit()
(code ref. 28). It is static, in that it will not change or move
for the entire runtime, and assumes the default lighting parameters
of the OpenGL library, whereby a single light shines from a source
just above the viewport. In this procedure, we also set the background
colour and tell OpenGL to favour an increase in the quality of perspective
calculations, even at a slight cost to performance14,
using glHint().
|
the OpenGL
display function
|
|
We declare the glDisplay()
function (code ref. 29) as the main OpenGL scene drawing procedure.
We have informed GLUT (code ref. 26) that, when we post that an
update is necessary, this function will generate the new scene.
At the procedure’s entry, the scene is void and, at return, should
be completely spawned. Since the majority of our scene centres around
the drawing of an instrument, most of the work is done by the draw()
function of the active instrument, which we will discuss later.
|
orientation and other
global display settings
|
|
Instead, more global functions, such as the ‘camera’ rotation and
zoom, are handled in the glDisplay()
procedure. The parameters for such operations are stored in global
variables, which can be set or modified anywhere in the MIVI scope.
Because of OpenGL’s relative vector-based architecture (see Appendix
B), implementing zoom is almost as simple as a single translation
backwards. However, to maintain an efficient scene, OpenGL requires
programs to specify a viewing range. Setting up the viewing matrix
through gluPerspective(),
requires you to specify the field of view (FoV), the pixel aspect
ratio and both a near and far clipping distance. Objects (1) outside
the FoV, (2) before the near clipping-distance or (3) beyond the
far clipping-distance are, thus, simply not rendered. We must make
sure that the translation to account for zoom does not place the
ensuing instrument beyond the far clip distance. Thus, we use the
zoom variable in setting
this limit.
Finally, immediately before the instrument’s draw()
function is called, three calls to glRotatef()
implement the instrument’s rotation in the z, y and x planes, respectively.
Each rotation will effect the current viewing matrix, thereby impacting
on future rotations and, thus, the order of these rotations is important.
The z-axis is only modified during SPIN_MODE,
which, when active, simply increments the rotation around all 3
dimensions during the idle()
call. This axis will therefore tend to have less influence on the
other two than they have on it, and is thus executed first. The
remaining ordering of the x and y rotations is such as it is to
present the most intuitive response to the user’s manual control
of these parameters through the interface (see section 5.3).
|
|
|