Whenever I make an appearance on one of the Be user mailing lists, I usually get peppered with questions from the curious about NetPositive. Many of these questions have common themes, so I thought I'd answer a number of them here.
I'm sure you've all noticed a certain emphasis on the Media Kit in Developer Workshop columns over the last few months. So far we've talked a lot about nodes, but not much about how to control their behavior from the application level. Many commonplace nodes that media applications will deal with on a regular basis have various parameters that can be adjusted at run time. This week I'll show you how to present a user interface for dynamically controlling a node.
But...before you run off to try the techniques I'll demonstrate here, I must warn you that like so many of our new Media Kit tools, today's code will only work under Release 4.1, not under Release 4. Feel free to follow along at home, though, and as soon as you get your hands on Release 4.1 you can see the glorious results in living color!
Right—the code. You can find the ParameterSample application right here:
ftp://ftp.be.com/pub/samples/media_kit/ParameterSample.zip
This little application displays a pair of windows. One is a duplicate of
the Audio preferences panel's Mixer tab, and the other contains a single
slider control for adjusting the master gain of the system mixer. All the
controls are "live"; adjusting the sliders while sounds are playing
adjusts the volume in real time. And the secret to all this live
instrumentation is the magic of BParameters
.
Any Media Kit node that has an adjustable configuration can express that
configuration in terms of BParameter
s, objects which
each describe a single variable parameter. The set of all
BParameter
s that a node supports is called its
"parameter web," and (sensibly enough) is published via a class
called BParameterWeb
. Each
BControllable
node in the system has exactly one
BParameterWeb
; if a node is not configurable, its
BParameterWeb
will contain no
BParameter
s. You can see whether a given node is
controllable by checking (media_node::
), which will be non-zero if the
node is controllable.
kind
&
B_CONTROLLABLE
The BParameterWeb
is not simply a list of
BParameters
. Because some parameters are linked --
for example, the gain and mute controls for a particular audio channel --
the BParameterWeb
class uses a hierarchical
organization in which BParameter
s are contained by
BParameterGroup
s, which themselves can contain other
BParameterGroup
s; the end result is rather like an
organization of files into a directory structure. This organization of
parameters into groups is intended as a "hint" to the user
interface to guide the presentation of related parameters.
The Media Kit also supports standardized presentations of media-related
controls. This ensures that all media-related instrumentation the user
sees will have a consistent look and feel. The Kit uses an object called
a BMediaTheme
to manage the creation of
appropriate BControl
objects for
configuring nodes. The design of BMediaTheme
s is a topic for the future;
for today, we'll just use the default theme to get the controls for our
node.
Oh—our node? That's true, we need to pick a node to control, preferably one that does something noticeable. The ParameterSample app manipulates the global system mixer's node, just like the Audio preferences panel.
The easiest way to present a UI for a node's parameter web is to ask a
BMediaTheme
to build one for you. This is the approach implemented in the
EasySampleWin
class. Let's look at the relevant code, all of which is in
the window's constructor.
media_nodemixerNode
;BMediaRoster
*roster
=BMediaRoster
::Roster
();roster
->GetAudioMixer
(&mixerNode
);roster
->GetParameterWebFor
(mixerNode
, &mParamWeb
);
This is straightforward: We look up the global BMediaRoster
object, ask
it for a reference to the global system mixer's node, then get a copy of
the node's BParameterWeb
.
BMediaTheme
*theme
=BMediaTheme
::PreferredTheme
();BView
*paramView
=theme
->ViewFor
(mParamWeb
);AddChild
(paramView
);
That's it! We ask for the preferred theme, then request a
BView
from the BMediaTheme
that can manage the full BParameterWeb
. Once this
view is added to the window, your application needn't do anything with it
-- all messages and parameter updating are handled internally to the
BView
. As a user, you'll see that this window
contains a clone of the Audio preferences panel's mixer controls. This is
because—surprise!—the Audio preferences panel uses exactly this
technique to build its BView
s.
But what if you don't want to instrument the entire
BParameterWeb
? What if you only want to present a UI
for a single BParameter
? Well, life gets a bit more
complex in that case. Instead of creating a BView
to
handle a single parameter, a BMediaTheme
will create
a single BControl
. The responsibility for handling
that BControl
's placement belongs to your
application. More significantly, handling that control's *semantics* is
also the app's responsibility—the app must respond to and interpret the
BControl
's messages, and change the
BParameter
's value in response to those messages.
The HarderSampleWin
class displays a window
containing just the master gain slider. Let's look at the differences from
the easy case. The constructor begins the same way, by looking up the
mixer's BParameterWeb
, but then things get
complicated:
int32numParams
=mParamWeb
->CountParameters
();BParameter
*param
=NULL
; for (int32i
= 0;i
<numParams
;i
++) {BParameter
*p
=mParamWeb
->ParameterAt
(i
); if (!strcmp
(p
->Kind
(),B_MASTER_GAIN
)) {param
=p
; break; } } if (param
->Type
() ==BParameter
::B_CONTINUOUS_PARAMETER
) {mGainParam
= dynamic_cast<BContinuousParameter
*>(param
); }
This searches the BParameterWeb
for a specific
BParameter
; in this case, the master gain parameter.
This is easy because there is only one such parameter in the mixer's web.
If you're looking for a particular BParameter
from a
given parameter web, there are other mechanisms you can use to locate the
one you're interested in, such as looking at
BParameter
::ID()
and matching that against the
source or destination ID supplied with your connection to the node.
Note the dynamic_cast
. In general, you won't know the derived
type of the BParameter
that the web returns to you.
The BParameter
::Type()
method gives you the
information you need to determine which derived class the object really is,
and, therefore, how to manipulate it. Our example is simplified by knowing
that the system mixer's master gain control is always a
BContinuousParameter
, but it would be a
straightforward matter to write code to handle any of the
BParameter
subclasses appropriately.
Once we've found the right BParameter
, we need a control that is
appropriate for manipulating it:
BMediaTheme
*theme
=BMediaTheme
::PreferredTheme
();BControl
*control
=theme
->MakeControlFor
(mGainParam
);mGainControl
= dynamic_cast<BMultiChannelControl
*>(control
);mGainControl
->SetMessage
(newBMessage
(gControlMessage
));
That gives us the control, and ensures that its value-changed messages have
a known "what" code for our message handler to recognize. Note
that the control is of type BMultiChannelControl
.
This class is new in R4.1. As its name suggests, it provides an interface
for controlling arbitrarily many "channels" of data from within a
single BControl
. In our case, it will hold two
channels of data, because the system mixer is stereo. Controls derived from
BContinuousParameter
will generally be derived from
either BSlider
or
BMultiChannelControl
—a general-purpose
application will need to be able to handle all possibilities—but for
purposes of the example we assume that the control is a
BMultiChannelControl
, because the system mixer is
stereo.
Now, before we're done constructing the view, a little math:
floatparamMin
=mGainParam
->MinValue
(); floatparamMax
=mGainParam
->MaxValue
(); int32controlMin
,controlMax
;mGainControl
->GetLimitsFor
(0, &controlMin
, &controlMax
);mScale
= (paramMax
-paramMin
) / (controlMax
-controlMin
);mOffset
=paramMax
-mScale
*controlMax
;
The BControl
's range and the
BParameter
's are not necessarily the same,
but they *are* guaranteed to map linearly. This formula is the result of
a little linear algebra that lets us express the parameter's min and max
values as a linear function of the control's min and max values.
That's it for setting up the window; the rest of the magic happens in the
window's MessageReceived()
method. When the slider
is moved, the window receives messages with our designated "what"
code indicating the new value of all channels of data managed by that
BMultiChannelControl
. The channel values are passed
in an array of int32s called
be:channel_value
. If the parameter is single-valued,
then there may only be the standard BControl
data,
under the name be:value
. A well-behaved application
should be prepared to handle either case:
int32chan1
,chan2
; status_terr
=msg
->FindInt32
("be:channel_value", 0, &chan1
); if (!err
)err
=msg
->FindInt32
("be:channel_value", 1, &chan2
); else {err
=msg
->FindInt32
("be:value", &chan1
);chan2
=chan1
; }
Next, we use the scaling factors that we calculated earlier to transform
the slider's values to the BParameter
's expected
range. BParameter
s take
floating point values, by the way!
floatchanData
[2];chanData
[0] =mScale
*chan1
+mOffset
;chanData
[1] =mScale
*chan2
+mOffset
;
Finally, we're ready to set the parameter's new value. Like so many other
things in the Media Kit, setting a BParameter
's value takes a performance
time at which to apply the change. We want the change to be immediate, so
we look up the current time according to the node's time source, and set
the parameter:
BTimeSource
*timeSource
;BMediaRoster
*roster
=BMediaRoster
::Roster
();timeSource
=roster
->MakeTimeSourceFor
(mParamWeb
->Node
()); bigtime_tnow
=timeSource
->Now
();mGainParam
->SetValue
(&chanData
,sizeof
(chanData
),now
);
Voilà! An active, effective slider controlling the master system gain without benefit of the Audio preferences panel!
Well, okay, it's not the world's most practical application (although a pop-up Replicant to tweak the master volume would be pretty handy), but of course it's just a convenient controllable node to illustrate the principles. Other nodes that media applications will find themselves presenting interfaces for with may include video compression, audio devices including multichannel cards, video capture, and so forth. Now as soon as you get Release 4.1 you'll be all set to build the UI of everyone's dreams!
There's more that could be done with these implementations, too. Neither
one of them watches the BParameterWeb
to see if it changes, and so the
dynamic updates that you see in the Audio preferences panel won't appear
in these sample windows. Also, none of these views are "aware" of
parameter value changes that are made in other views—so if you adjust
the master gain in the Audio preferences panel, you won't see the sliders
move in the sample windows. Perhaps a future Developer Workshop will
address these additional features...
One would hope to answer this question in the affirmative, but before I elaborate, some follow-up to last week's column, "A Crack in the Wall," along with our thanks.
Our offer of free copies of the BeOS to OEMs willing to load our OS "at the factory," on the hard disk of PCs they sell, got a tremendous response. We appreciate the interest in our product and we intend to do our best to honor the hospitality extended to us. Watch this space or, more generally, www.be.com, for more details. For a number of contractual reasons, this offer applies only in the US and Canada, not to other countries in the Americas or in Asia. For Europe, please contact our VP Europe, Jean Calmon, jcalmon@beeurope.com, for country-by- country details.
As we collect data from the flow of responses, an interesting but not unexpected picture emerges. The OEMs expressing interest are the ones who cannot realistically be "fined" by Microsoft—that is, lose their Windows rebate. If you pay the maximum OEM price for Windows, or close to it, you won't be afraid to load Linux or the BeOS on your customers' hard drives, especially if you don't have to account to Wall Street for your actions.
If, on the other hand, your exposure is measured in millions of dollars per quarter, and you are the CEO of a publicly traded company, you'll load Windows and nothing but Windows on the PCs you sell. More precisely, you might load Linux as the OS engine on hardware other than PC servers. In any event, this represents only a preliminary look at the returns -- it's too early to draw definite conclusions.
Now, let's turn to the customer in the title of this column. We hear that the Windows monopoly is good for customers—it's a standard, there's no confusion, users can rely on a trusted foundation for their work, and so on. But how can this be if there are so many obstacles placed in the way of a customer's even seeing that (s)he has some (limited) choices?
I'll take one example of what I mean by choices. One overseas OEM announced with great fanfare that it would offer some configurations in its PC line with a dual-boot arrangement: Windows 98 for mainstream applications and the BeOS for its natural media uses. Great—exactly what we wanted—the specialized media OS peacefully coexisting with the mainstream platform.
Well, not exactly. If you take the machine out of the box and boot it, the BeOS is nowhere to be seen—the computer boots only Windows 98. If you read the documentation carefully, you'll find out how to "unhide" the BeOS. Then, through a complicated sequence, you'll finally get to the dual-boot situation. Should the OEM be criticized for this state of affairs? Again, not exactly. It appears that the fear of losing Windows rebates intervened to prevent the customer from being offered a genuine dual-boot system. In fact, as we verified for ourselves, the steps the customer must perform are so complicated that it's much easier just to do the simple partition and BeOS installation possible with our retail product, complete with a BeOS Launcher icon on the Windows desktop.
Wouldn't one think that Microsoft behaves, in effect, as if the PC belonged to it, rather than to the OEM or to the customer? It's is hard to see how the customer and, more generally, the industry, benefit if one company decides what's good for all, and what the customer should see or not see.