Issue 3-22, June 3, 1998

Be Engineering Insights: Writing A Sound Card Driver

By Marc Ferguson

A number of people have expressed interest in writing sound card drivers for BeOS. This article describes the interface used by the current audio server to communicate with sound card drivers. Note that this interface is for the current audio server, and that the current audio server will be replaced with something better in a future release. But if you want to write a sound card driver and test it with the current audio server, this is what you will have to support.

These are the ioctl codes used by the audio server:

#include <Drivers.h>

enum {
  SOUND_GET_PARAMS = B_DEVICE_OP_CODES_END,
  SOUND_SET_PARAMS,
  SOUND_SET_PLAYBACK_COMPLETION_SEM,
  SOUND_SET_CAPTURE_COMPLETION_SEM,
  SOUND_RESERVED_1,   /* unused */
  SOUND_RESERVED_2,   /* unused */
  SOUND_DEBUG_ON,     /* unused */
  SOUND_DEBUG_OFF,    /* unused */
  SOUND_WRITE_BUFFER,
  SOUND_READ_BUFFER,
  SOUND_LOCK_FOR_DMA
};

The SOUND_SET_PLAYBACK_COMPLETION_SEM ioctl takes a (sem_id*) argument which points to a semaphore that must be released once for each buffer written. The semaphore should be released when the data in the buffer is no longer needed.

The SOUND_SET_CAPTURE_COMPLETION_SEM ioctl takes a (sem_id*) argument which points to a semaphore that must be released once for each buffer read. The semaphore should be released when the data in the buffer is valid.

The SOUND_WRITE_BUFFER ioctl takes an (audio_buffer_header*) argument which is defined in MediaDefs.h:

typedef struct audio_buffer_header {
  int32 buffer_number;
  int32 subscriber_count;
  bigtime_t time;
  int32 reserved_1;
  int32 reserved_2;
  int32 reserved_3;
  int32 reserved_4;
} audio_buffer_header;

The audio data immediately follows the audio_buffer_header in memory and is in stereo signed 16-bit linear native-endian format. The size in bytes of the audio data plus the audio_buffer_header is stored in the reserved_1 slot of the audio_buffer_header (the audio server was written before the size argument to the ioctl call was implemented). The size and address of the audio data can be derived this way:

audio_buffer_header* header = (audio_buffer_header*) ioctl_arg;
int32 bytes_of_data = header->reserved_1 - sizeof(*header);
int16* addr_of_data = (int16*) (header + 1);

The driver can ignore the buffer_number and subscriber_count slots of the buffer header and should store an estimate of the system_time() corresponding to the beginning of the buffer in the time slot of the buffer header.

The SOUND_WRITE_BUFFER call is allowed to return before the data in the buffer has been consumed but the playback completion semaphore must be released when the buffer can be recycled.

The SOUND_READ_BUFFER ioctl takes an (audio_buffer_header*) argument. The size and address of the data can be computed as above. The time slot should be written with the estimate of the system_time() corresponding to the beginning of the buffer. The call is allowed to return before the buffer is full but the capture completion semaphore must be released as soon as the buffer is full.

The SOUND_GET_PARAMS and SOUND_SET_PARAMS ioctls read and write a set of parameters which correspond to the settings in the sound preferences panel. The ioctl argument is a (sound_setup*) in the following format:

enum adc_source {
  line = 0, cd, mic, loopback
};

enum sample_rate {
  kHz_8_0 = 0, kHz_5_51, kHz_16_0, kHz_11_025, kHz_27_42,
  kHz_18_9, kHz_32_0, kHz_22_05, kHz_37_8 = 9,
  kHz_44_1 = 11, kHz_48_0, kHz_33_075, kHz_9_6, kHz_6_62
};

enum sample_format {};  /* obsolete */

struct channel {
  enum adc_source adc_source;
       /* adc input source */
  char adc_gain;
       /* 0..15 adc gain, in 1.5 dB steps */
  char mic_gain_enable;
       /* non-zero enables 20 dB MIC input gain */
  char cd_mix_gain;
       /* 0..31 cd mix to output gain in -1.5dB steps */
  char cd_mix_mute;
       /* non-zero mutes cd mix */
  char aux2_mix_gain;
       /* unused */
  char aux2_mix_mute;
       /* unused */
  char line_mix_gain;
       /* 0..31 line mix to output gain in -1.5dB steps */
  char line_mix_mute;
       /* non-zero mutes line mix */
  char dac_attn;
       /* 0..61 dac attenuation, in -1.5 dB steps */
  char dac_mute;
       /* non-zero mutes dac output */
};

typedef struct sound_setup {
  struct channel left;
       /* left channel setup */
  struct channel right;
       /* right channel setup */
  enum sample_rate sample_rate;
       /* sample rate */
  enum sample_format playback_format;
       /* ignore (always 16bit-linear) */
  enum sample_format capture_format;
       /* ignore (always 16bit-linear) */
  char dither_enable;
       /* non-zero enables dither on 16 => 8 bit */
  char mic_attn;
       /* 0..64 mic input level */
  char mic_enable;
       /* non-zero enables mic input */
  char output_boost;
       /* ignore (always on) */
  char highpass_enable;
       /* ignore (always on) */
  char mono_gain;
       /* 0..64 mono speaker gain */
  char mono_mute;
       /* non-zero mutes speaker */
} sound_setup;

On PPC systems the mic_attn and mic_enable parameters are used to control the amount of adc to dac "loopback" instead of the microphone input level.

The SOUND_LOCK_FOR_DMA ioctl takes an (audio_buffer_header*) argument but can be ignored except on Macintosh. A Macintosh driver should call lock_memory() on the audio buffer with the B_DMA_IO flag.

The audio server opens the driver named /dev/old/sound. So when you have implemented these ioctl calls and you want to test your driver with the audio server, you can either name it /dev/old/sound or name it something like /dev/old/mydriver and create a symbolic link in your UserBootscript file:

ln -s /dev/old/mydriver /dev/old/sound

The "old" in this path name is to remind you that this is an interface which will be deprecated in the future.


Developers' Workshop: DynaDraw, Part Two

By Michael Morrissey

In the previous article on DynaDraw, we built a no-frills app—a basic window and a view, and our drawing algorithm. In this article, we get to the fun part—adding lots of controls which modify how the calligraphy strokes are drawn.

Download the code from:

ftp://ftp.be.com/pub/samples/interface_kit/obsolete/dynadraw2.zip,

compile it, and see how the new features work before getting into the details of this article. I especially dig wireframe mode, which calls StrokePolygon instead of FillPolygon.

Let's start out the way we did last time, looking at what we'd like to do, and what we'll need to do it:

First, we'd like a menu bar across the top of the window. Under it, we'll put two menus: File and Controls. Under File, we'll have the items About, which brings up an About Box, and Quit, which, well, quits. Under Controls, we'll have three items: Tweakables, which brings up a panel for controlling the mass, drag, and other parameters; Color, which allows us to change the color of the pen; and Clear Screen, which clears our drawing.

The Tweakables panel and the Color panel will each have their own windows. R3 introduces a new window type, B_FLOATING_WINDOW, which is perfect for control windows. Floating windows have smaller borders and tabs than regular windows, and they are active whenever the application is active.

The next question is: which object should be responsible for managing these control windows? It's possible to add these duties to the DDWindow class, but if our application grows to include more windows, this approach will be cumbersome. A better solution is to subclass BApplication in an object which manages the creation and destruction of the control windows.

Here's our object list now:

Messaging

Good object layout is half the battle, and the other half is good messaging. The heart of this entire application lies in the MessageReceived() functions in each of the classes listed above. Reading those functions will tell you what duties those objects perform, so let's go over them now.

Our application object handles B_ABOUT_REQUESTED, and B_QUIT_REQUESTED, which is standard. It also handles TWEAK_REQ and COLOR_REQ, which are requests to open the tweakables window and the color window. These messages (or more properly, commands, because they are messages which contain no data) come from DDWindow, which sends them in response to the user selecting the appropriate menu item. Our application object also handles TWEAK_QUIT and COLOR_QUIT, messages sent from the tweakables window and from the color window the user has closed them.

ColorWin has only one specific message to deal with: COLOR_CHG, which is a message we requested the BColorControl send us when the user has chosen a new color. (This is specified in the BColorControl constructor.) In this case, we want to alert the DDWindow of the new color selection, so we pack the RGB value of the user's selection into the message:

   case COLOR_CHG:
    /* get the current color settings,  */
    /* attach to the message            */
    /* and pass it along to the handler */
    rgb_color clr = cc->ValueAsColor();
    swatch->SetViewColor(clr);
    swatch->Invalidate();
    msg->AddInt16("red", clr.red);
    msg->AddInt16("green", clr.green);
    msg->AddInt16("blue", clr.blue);
    handler->PostMessage(msg);
    break;

So our message is really getting double-duty: it serves as a command from the BColorControl object to ColorWin indicating a color change, and it serves as a message (with the RGB value as data) from ColorWin to DDWindow.

TweakWin's MessageReceived() is more of the same. It handles MASS_CHG, DRAG_CHG, WIDTH_CHG, and SLEEPAGE_CHG, which arrive from the BSliders created in TweakWin's constructor. Again, an int32 value is added to each of these messages, and the message is then sent to the DDWindow object.

TweakWin also generates FILL_CHG and ANGLE_CHG messages, >from the Fill and Angle check boxes. We don't need to add any data to these messages, so we can send them directly on to the target, DDWindow, rather than having TweakWin::MessageReceived() pass the message along to DDWindow:

  fill = new BCheckBox(BRect(25,320,135,340), "fill",
                      "Wireframe", new BMessage(FILL_CHG));
  fill->SetTarget(handler);
    /* send FILL_CHG directly to handler */

SetTarget() is a function defined in BInvoker, which is a subclass of BCheckBox.

This technique is also used in the menus of DDWindow. Since we want B_ABOUT_REQUESTED and B_QUIT_REQUESTED to be sent along to the application object, we can use a shortcut function, SetTargetForItems(), which will send the messages of all menu items in the File menu to be_app:

  BMenu* menu = new BMenu("File");
  menu->AddItem(new BMenuItem("About",
                new BMessage(B_ABOUT_REQUESTED)));
  menu->AddItem(new BMenuItem("Quit",
                new BMessage(B_QUIT_REQUESTED)));

  /* Both the About and Quit messages should be directed
     to be_app */

  menu->SetTargetForItems(be_app);
  mb->AddItem(menu);

In the Controls menu, although we want TWEAK_REQ and COLOR_REQ to go to be_app, we want CLEAR_SCREEN to post to DDWindow, so we specify the targets one at a time:

  menu = new BMenu("Controls");
  BMenuItem* tmpItem = new BMenuItem("Tweakables",
    new BMessage(TWEAK_REQ));
  tmpItem->SetTarget(be_app);
  menu->AddItem(tmpItem);

  tmpItem = new BMenuItem("Color", new BMessage(COLOR_REQ));
  tmpItem->SetTarget(be_app);
  menu->AddItem(tmpItem);

  menu->AddSeparatorItem();
  menu->AddItem(new BMenuItem("Clear Screen",
    new BMessage(CLEAR_SCREEN)));

  /* Target not specified for Clear, so the target will
     be DDWindow */

  mb->AddItem(menu);
  AddChild(mb);

Notice that in DDWindow::MessageReceived(), we simply pass all of the messages from the tweakables window and the color window to the FilterView, which actually interprets them. Note how values are extracted from messages in FilterView's MessageReceived():

   case COLOR_CHG:
     int16 red, green, blue;
     msg->FindInt16("red", &red);
     msg->FindInt16("green", &green);
     msg->FindInt16("blue", &blue);
     SetHighColor(red,green,blue);
     break;

BSliders

R3 introduces the BSlider class, an incredibly useful control device. It's really flexible too, allowing you to customize the look of the thumb, the tick marks, the track, and more. Since BSlider only deals with integers, you may need to do some small tricks to get the value you want, such as setting up a slider with a range of values between 0 and 100, and using this as a percentage of your maximum value. This is what we're doing with Mass, Drag, and Width.

Also, BSliders are set up to have the left hand side be the minimum value, and the right hand side as the maximum value. Sometimes this isn't what you want to do: in the case of "Sleepage", it seemed more natural to me to "sleep less" towards the right of the control, and "sleep more" towards the left.

In this case, just set the slider up to go from your minimum to your maximum, and when reading that value, subtract it >from your maximum, and add that value to your minimum. In the sleep case, I set up the slider to go from 3000 microseconds to 30000 microseconds, and then (in FilterView::MessageReceived()) I subtract the slider value from 30000 (the maximum), and add the difference to 3000 (the minimum).

Future Enhancements

This program can be extended in a variety of ways, and I encourage you to do so. Some small projects might include letting the user control the background color of the FilterView, and dynamic updating of the color swatch. Larger projects include saving the drawing to disk, printing, and letting the user undo brush strokes. Experiment and enjoy!


You're Contradicting Yourself!

By Jean-Louis Gassée

We've "heard" this on e-mail and from newsgroups. We're likely to hear it again at PC Expo, so I thought I'd take a few minutes to clarify our position. The contradiction astute observers point out is this: On the one hand we present ourselves as a media OS, a specialized platform for audio and video applications. We sometimes use the "A/V Linux" sound byte -- and then proceed to show off "non-A/V" applications on our site and, soon, at our booth. Come on, they say, you're really trying to be a general purpose OS—you're competing with Microsoft, you just don't want to admit it!

Let's address the "non-A/V" applications first. We believe the "tractor apps" will come from the field of audio and video activities—editing, synthesizing, producing, and mangling digital media. However, this belief doesn't imply that the BeOS can't or shouldn't be put to other uses.

Imagine for a moment that you're a software developer and all you care about is word processing. Your mother said you shouldn't, but you do, either to aggravate her or just because it's in your soul. Let's further assume you want to make money at it, presumably to show dear old mom who's right. Are you going to try writing a Windows word processor? Of course not. Microsoft Office is the monarch and you'll never impress your mother.

On the BeOS though, the adventure might look risky, but not suicidal. And you can allay family scepticism by telling them about the advantages of Electronic Software Distribution (ESD) and how BeDepot.com is for software authors without knee pads.

"Non-A/V" applications for BeOS have another advantage: users will be happier with their BeOS system, even if their primary use is the "official" media driven one. They still want word processing and e-mail.

Further, imagine a page layout program on the BeOS. It's likely to be livelier, faster, more reactive, and more available to the user than its venerable elders are on the Mac or Windows now. The better feel of the program, its real-time WYSIWYG behavior comes from the programmer, of course. But it also makes a good case for what the BeOS platform has to offer: a good combination of multithreading and—on the right hardware -- multiprocessing, fast graphics, and good APIs.

But, keen observers will insist, this makes you a general-purpose OS, non? Therefore, you're competing with Microsoft and, therefore, you're dead—you just don't realize it yet.

Ah, but this is sophistry. Yes, computers are programmable, in fact they can all be simulated by a simple Turing machine. As a result, all hardware and all system software can do anything. All true enough, if we don't consider how much time, aggravation, or money is involved. So, in the general-purpose office productivity field, I don't believe anyone can beat Microsoft for the time being. Windows, with or without Internet Explorer, Office, Money... your everyday needs are well covered and OEM relationships are such that no new entrants can survive.

But, if Windows can "do" audio and video, it doesn't do it very well. And Windows 98 shows little sign of anything but marginal improvements on what Windows 95 offered. That's where we complement Windows, and the fact that Be developers offer a broader range of applications doesn't make us a competitor to Microsoft Office. In other words, just as Windows can "do" audio and video but doesn't shine there, we can "do" more than A/V, but that's not where we will win.

So, our priority continues to be the A/V domain, where we are most useful. And, when we like a page layout program or an e-mail client on the BeOS, that sentiment doesn't change the nature of our platform.


BeDevTalk Summary

BeDevTalk is an unmonitored discussion group in which technical information is shared by Be developers and interested parties. In this column, we summarize some of the active threads, listed by their subject lines as they appear, verbatim, in the mail.

To subscribe to BeDevTalk, visit the mailing list page on our web site: http://www.be.com/aboutbe/mailinglists.html.

NEW

Subject:BLooper/multiple inheritance

Q:

Is this a bug? AK

A:

BLooper and multiple inheritance??

Multiple inheritance questions:

Q:

Given...

class C : public A, public B {...}

...why does the A destructor not get called when a C object is deleted?

A:

Most likely because the A destructor probably isn't declared virtual. The same problem holds for single inheritance: If you use a base class pointer (A) to point to a derived instance (C) and then delete A, the C destructor will only be called if the A destructor is virtual. (And note that virtual destructor declaration is inherited; if virtual ~A(); then the destructors of all classes that inherit from A will be virtual.)

Q:

What's the trick to getting the "diamond" problem (D derives from B and C; B and C both derive from A) to compile properly?

A:

B and C derivation (from A) must be virtual:

class B : virtual public A { ... }
class C : virtual public A { ... }

Otherwise, D will end up with two A instances.

Subject: BTextControl cannot be made uneditable

Wendell Beckwith has noticed that you can't set a BTextControl's text view object to be uneditable until after the control has been added to its window. In other words, this is no good:

control->TextView()->MakeEditable(false);
window->AddChild(control);
/* The text view is still edible. */

Why should this be? Peter Potrebic suggests that instead of setting the text view's editable state, you should simply set the control to be disabled. But, responded Mr. Beckwith, disabling the control affects the control's label (it turns gray). Why should the two states (text editability and label appearance) be tied together?

Subject:Shared libs on Intel

From Sander Stoks, a question and a plea:

I searched the FAQ, searched BeGeek.com, tried everything. How does one make (use of) a shared lib on R3/Intel? [And] _please_ don't tell me that I will have to add __declspec() stuff to all that code...

Jon Watte responded to Mr. Stoks question, but couldn't satisfy the no-declspec request:

“The easiest fix is to have a file Exports.h which __declspec()-s all your classes, and include this file first in each and every one of your header files. You also need to __declspec(dllimport) the same entities that you previously exported when you get around to using the shared library.

Also, you need to include the init_term_dyn.o file among your libraries for static initialization to happen correctly.”

Chris Herborth seconded the spirit of Mr. Stoks' letter:

Just fix the damn development tools. >}:-( There should be NO DIFFERENCE when building an app/library/shared library for BeOS, whether you're on PowerPC or x86.

There was a groundswell of support for the position, which is, in essence, that Be should concentrate more on the performance, robustness, and portability of the development tools. Fred Fish gave a reasoned response to the escalating criticism:

Although I'm not thrilled about the current tools situation with all the 'windows cruft', I've learned to hold my nose and just get on with work... I'm still hoping that someday we will have the opportunity to either hide this stuff better or dump it entirely. Whether that point is months from now or a few years down the road, I think it will eventually happen.

So how should Be go about "transitioning" to new tools? Wendell Beckwith says drop the hammer:

If Be ... should redraw the line for backwards compatibility and adopt a single object/executable file format that works across all of their architectures, then I believe the users/developers would rather bite the bullet now than wait until later when Be software is more numerous and doing something like this will be much harder to pull off.

The thread concentrated, thereafter, on itchy symbol exportation/library inclusion details. For more info on the topic, see "Project Libraries" and "Building a Library" in http://www.be.com/documentation/rel_notes/R3DevLayout.html

Subject:Wizards

Does the BeOS have any "Wizard" API? (According to Mark B. Elrod, a Wizard is a “dialog which guides you through a process...It is most often used for installers and things that need to gather info.”)

You can, of course, build a Wizard-like UI yourself from windows, textviews, buttons and bows. But should there be a specialized panel class that knows how to draw and apply back/forward labels to successive "slides"? Factotum Jon Watte rushed out and wrote one, damn it: ftp://ftp.b500.com/pub/BeOS/wizard.zip

Subject:What happened to the inherited keyword?

It's dead. But, as Jon Watte pointed out:

You can use templates for the same effect. Or just make the rule that every class...should have a private typedef of inherited:

class MyClass : public BView
{
public:

private:
    typedef BView inherited;
};

Making it private means it won't clash with further subclasses of MyClass.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.