An article in a previous newsletter (Issue 36) contained sample code for the Nino class, a simple audio test bed which copies audio from the ADC input stream to the DAC output stream.
The audio API has changed slightly in the Preview Release. This article will illustrate the more important API changes by showing how the Nino class should be rewritten for the Preview Release.
The old Nino class definition contained a buffer, two semaphores for
controlling the stream functions, and two BAudioSubscriber
objects, one
for the ADC stream and one for the DAC stream. In the Preview Release,
each BAudioSubscriber
object has been replaced by two new objects, a
generic BSubscriber
object and either
a BADCStream
or BDACStream
object.
Thus the new Nino
class contains one BADCStream
,
one BDACStream
, and two
BSubscribers
:
structNino
{Nino
();~Nino
(); short*adc_buffer
; sem_idbufferReady
; sem_idbufferUsed
;BADCStream
*adcStream
;BDACStream
*dacStream
;BSubscriber
*adcSubscriber
;BSubscriber
*dacSubscriber
; };
There are two "stream functions," one for each stream, called
adc_reader()
and dac_writer()
.
The stream functions have a new "header"
argument which provides access to a timing estimate for each buffer, but
otherwise they work as before. adc_reader()
uses the bufferReady
semaphore to indicate that an input buffer has been received, and
dac_writer()
uses the bufferUsed
semaphore to signal that it has finished
merging the input buffer into the output buffer.
static booladc_reader
(void*arg
, char*buf
, size_tcount
, void*header
) {Nino
*my_nino
= (Nino
*)arg
;my_nino
->adc_buffer
= (short*)buf
;release_sem
(my_nino
->bufferReady
);acquire_sem
(my_nino
->bufferUsed
); returnTRUE
; } static booldac_writer
(void*arg
, char*buf
, size_tcount
, void*header
) {Nino
*my_nino
= (Nino
*)arg
; short*dac_buffer
= (short*)buf
;acquire_sem
(my_nino
->bufferReady
); for (inti
= 0;i
* sizeof(short) <count
;i
++) { int32sample
= (int32)dac_buffer
[i
] +my_nino
->adc_buffer
[i
]; if (sample
> 0x7FFF)sample
= 0x7FFF; if (sample
< -0x7FFF)sample
= -0x7FFF;dac_buffer
[i
] = (short)sample
; }release_sem
(my_nino
->bufferUsed
); returnTRUE
; }
To simplify the example, these functions assume that both streams have
the same buffer size. Be careful not to erase whatever may already be in
the output stream. dac_writer()
is being a good citizen and mixing the
ADC stream into the output rather than overwriting it.
In the Preview Release, the ADC stream and DAC stream always contain
16-bit stereo samples. The functions SetADCSampleInfo()
and
SetDACSampleInfo()
no longer exist. The sampling rate can still be
changed. The default sample rate is 44.1kHz and this rate should be used
when practical.
The Nino constructor initializes the semaphores, streams, and subscribers and enters the subscribers into their respective streams.
Nino
::Nino
() {bufferReady
=create_sem
(0, "buffer ready");bufferUsed
=create_sem
(0, "buffer used");adcStream
= newBADCStream
();dacStream
= newBDACStream
();adcSubscriber
= newBAudioSubscriber
("Nino ADC reader");dacSubscriber
= newBAudioSubscriber
("Nino DAC writer");adcSubscriber
->Subscribe
(adcStream
);dacSubscriber
->Subscribe
(dacStream
);adcSubscriber
->EnterStream
(NULL
,FALSE
,this
,adc_reader
,NULL
,TRUE
);dacSubscriber
->EnterStream
(NULL
,TRUE
,this
,dac_writer
,NULL
,TRUE
); }
Note that the Subscribe()
function has been simplified in that there is
no longer a "clique" argument. EnterStream()
works as before.
The Nino destructor carefully waits for dacSubscriber
to exit before
deleting the semaphores:
Nino
::~Nino
() {dacSubscriber
->ExitStream
(TRUE
);delete_sem
(bufferReady
);delete_sem
(bufferUsed
);adcSubscriber
->ExitStream
(TRUE
); deleteadcSubscriber
; deletedacSubscriber
; deleteadcStream
; deletedacStream
; }
To test a Nino using an audio CD as input, open the sound preference
panel and select "CD" as the input source, turn down the input gain, and
turn off the loopback. Put an audio CD in your CD drive and use the
CDPlayer application to play it. You can now experiment with digital
audio processing by modifying the code in the dac_writer()
function.
To summarize, the most important changes which have to be made when
porting audio to the Preview Release are replacing the BAudioStream
object with a pair of objects (BSubscriber
and
BADCStream
or BDACStream
),
adding a "header" parameter to the stream functions, and always using the
16-bit stereo sample format.
I had a conversation with a friend of mine who works for a well-known international high-tech company. He told me a story about the way this company works. "There isn't a single guy in my group who knows what the ---- is going on. Management wants me to save them, but I know I don't know anything either." But, apparently he knows more than most of them. "I wrote an application note, I didn't think anything about it,' he said. "Two weeks later, my boss tells me that <name deleted> turned it in as his own work." He continued for several minutes, ending with, "What you know doesn't mean ----, it's all politics."
Maybe I'm just naive for being shocked at this. But it's a useful reminder of why I like working at Be so much. Knowledge is definitely more important than rank. Nobody could get away with that stuff here. I'm feeling insecure because I'm pretty sure the VP of sales is a better programmer than I am. I get PC configuration tips from the CEO. These people do not get their credibility from their titles. This atmosphere is what gives all of us the courage to test conventional wisdom by gambling on something that appeals to our common sense. Let's step over the gangrenous bodies of these other companies. Like they say, those who die in train wrecks are the ones who were afraid to get on a jet.
The DR9 file system has changed a lot. Besides everything else, the database-like structure you knew has been completely replaced. So, what to do if you were using the database?
The new, attributed file system still has features that can be used like a database. You can do queries. A query is just a set of Boolean operators connecting "<name> = <value>" strings, where <name> is an attribute name. A query can only be executed on a single volume at a time. Queries return lists of entry_refs, although this may change. Paths may become the standard currency for referencing things in the file system.
Here's an example, from BTSQueryWindow.cpp
in the sample app:
// Create a query object.BQuery
*query
= newBQuery
(); // Get the query string from the text box const char*queryString
=mQueryField
->Text
(); // Figure out which volume is selected.BMenuItem
*item
=mVolumeMenu
->FindMarked
(); int32 index = mVolumeMenu->IndexOf(item); constBVolume
volume
((dev_t)mDeviceList
.ItemAt
(index
)); // Define the query.query
->SetPredicate
(queryString
);query
->SetVolume
(&volume
); // Execute the query.query
->Fetch
();
The query is just a list of entry_refs. Here's how to show a list of
paths in a listbox, from BTSResultWindow.cpp
:
BEntry
entry
;BPath
path
; // Start at the beginning.mQuery
->Rewind
(); // Step through the entries while (mQuery
->GetNextEntry
(&entry
) ==B_NO_ERROR
) { // Get the path, put it in the listbox.entry
.GetPath
(&path
); const char*pathName
=path
.Path
();BStringItem
*item
= newBStringItem
(pathName);mFileListView
->AddItem
(item
); }
Once you figure out which file you want, you've got to scan for the
attributes you're interested in. Here's some code that puts all the
attributes for an entry_ref into a listbox, from
BTSAttrListWindow.cpp
:
// Make node from BEntry object.BNode
node
(mEntry
); while(node
.GetNextAttrName
(attrName
) ==B_NO_ERROR
) { char*name
= strdup(attrName
); // Get the attribute info (type, size)node
.GetAttrInfo
(attrName
, &info
); // Create a string to put in the listboxstrncpy
(type
, (char*)&(info
.type
), 4);sprintf
(listLine
, "\"%-32.32s\" \'%-4.4s\' %-9.9Ld",attrName
,type
,info
.size
);BStringItem
*item
= newBStringItem
(listLine
); // Add the item to the listboxmAttrListView
->AddItem
(item
);mAttrNameList
.AddItem
(name
); }
Adding and removing attributes is easy. Just figure out the name, type,
and data, and create a BNode
that references the file to which you want
to assign the attribute. A BNode
can be
created from a path, a BEntry
, an
entry_ref, a BNode
, or a
BDirectory
. Here's writing and reading
attributes, from BTSAttrView.cpp
:
// The window keeps track of the selected BEntry in the // mEntry member.BNode
node
(mEntry
); void*data
; size_tsize
; uint32type
; // Get the data, size, and type from the currently // displayed view.mCurView
->GetData
(&data
, &size
, &type
); // If it's not a new attribute, and the new attribute is // smaller in size, you have to remove the attribute to // resize it. if (!mNewAttr
)node
.RemoveAttr
(mAttrName
); // just write the data.node
.WriteAttr
(mAttrName
,type
, 0,data
,size
);
So, if you want a database table, an easy approach is to make a directory, and create one file per record. (Of course, there's no reason they have to be in the same directory. If your app needs to spread things around, go ahead!) The attributes on the files represent the columns of the record. Records created in this way don't have to all have the same columns. Also, remember when you overwrite an existing attribute that you may need to remove it first, if you want to reduce the size of the attribute.
Attributes can be indexed for fast searching. Only int32, int64, float, double, and string types can be indexed, and indices are placed on the volume. Also, when you create a new index, existing attributes don't get added to it; you have to rewrite the attributes to add them to the index. You should see a sample in ftp://ftp.be.com/pub/dr9/samples/IndexUpdate.tar.gz that will update indices for you.
A few of the current concerns with attributes. If you have a bunch of small attributes, they may be space inefficient. There's a ~780 byte reserved area for initial attributes (for a volume with 1024 byte blocks), but additional attributes will require an extra file block. Also, you can't have two attributes with the same name, which could compound the size problem. If you need to do this, you'll probably want to pack multiple attributes in a single buffer and store them under one name.
One thing that's still a little up in the air is, what is the standard
way to store complex attributes? Anybody can pull up a string attribute,
but what about a bitmap? Is the color space the first thing in the data
buffer? What about a BRect
? This becomes important if different apps want
to look at and edit attributes of unrelated files. There are also as yet
no standards for attribute names; how do you find an email address, is it
"email" or "e-mail"? If it becomes necessary, Be will publish guidelines.
One simple way to put just about anything in an attribute is to make it
archivable. Something that's archivable has a constructor that takes a
BMessage
and implements the
method. Since
messages can still be flattened to a buffer, you can store just about
anything as an attribute and have an easy way to recreate it, getting the
other benefits of archiving to boot!
Archive
(BMessage
* message
)
What else can be done with attributes? You've got to think differently, you've probably never before had a feature like this, built right into the OS to exploit. What about an OpenDoc-like document draft system, where old revisions of the document are stored as diffs in attributes? What about a compiler that puts function/method meta-data in attributes so you can search it easily? How can your apps share information to make them better? Any time your document/app has meta-data, consider putting it in an attribute instead of in the file.
Lastly, when should you use attributes, when should you use resources? Resources are not searchable. Attributes are less portable, as other filesystems may not support attributes. This problem with attributes will be mitigated by tools like zip that know how to include attribute information. When in doubt, use an attribute.
If you want to see a working demo using attributes, download ftp://ftp.be.com/pub/dr9/samples/AttrBrowser.tar.gz, a simple, database-browser type app that supports the editing of a few attribute types. To put a bitmap in an attribute, try dragging and dropping one from Rraster.
We've done the impossible, or at least something that is of historical significance to the company. We have completed our first 2 day World Wide Developer's conference. For those of you who attended, I hope you all had a good time and learned a lot. For those of you who couldn't make it, I hope you can make it to MacHack or Boston MacWorld.
We had all the drama that you would expect for a major event. Many engineers staying up late nights, demos not working until the last minute. Slides not coming up. But in the end, I think we made a good accounting for ourselves and disseminated a lot of very useful information. We had our first Master's awards program, the results of which will be posted on the web site shortly. We also had a chance to form new alliances, and impress a flock of new developers.
If you were at the Conference, you received a Early Access CD. Judging by our email, you've gotten it onto your machine without too much difficulty, and are now looking around for the documentation. You should also find on the CD a number of sample applications. There are some old familiar ones, as well as some DR9 specific new ones. You should be able to mine these sources for useful tips on DR9isms. In the coming weeks we will be working very hard to complete the documentation and write more sample apps; until then, send those pressing questions to DevSupport, via the online Developer Support form in the Registered Developers Area and we'll try to help you out. In many cases we might refer you to one sample app or another. If that doesn't help, then tell us so and we'll go further.
During any new release, there are questions and concerns. We'll do our best to keep you informed and programming towards the Preview Release.
Coming up, we have three days of conferences in Europe, Mac Hack in June, and MacWorld in August. This makes for a very busy summer. We want you programmers to work real hard, because the harder you work, the faster we can show your apps, and the faster they get exposure, the better chance they will have of making money for you so that you can continue to program for the BeOS.
Our short term job is not over yet. It's only just begun. This week we will be preaching to the Mac faithful at the Apple World Wide Developer's Conference. I look forward to what will hopefully be a favorable reception and a community building experience.
Again, for those of you who couldn't make this conference, keep up the good work, and we'll see you in Boston. For those of you who did make it, you now know that Yams truly does exist, and she is truly the littlest geek. She has the tatoos to prove it.
We are just emerging from last weekend's conference, a successful one. On the opening day, Saturday May 11th, we tallied over 650 developers coming from the US, mostly, but also Europe and Japan. This is, as a ZDNet report noted, twice as many as at our conference right after MacWorld, in January of this year. And, Sunday night, there were more than two thirds of the initial audience in the room for the closing session. On Mother's day... What this means depends upon your perspective. We have no life, no feelings of guilt, or the conference had something going for it. It wasn't the food, we made sure of that, nor the location, no Palm Springs in the lowlands of Santa Clara.
Seriously, how not to be ecstatic, just for a moment, before we return to our usual oscillations between excitement and anxiety. Last week, I wrote we were going to appear before a jury of our peers: skeptical, competent programmers. The interest they demonstrated throughout the entire conference was the best reward we could hope. It's one thing to believe in our work—we do, the belief has carried us through "interesting" times. Feeling the enthusiasm of people we respect takes our motivation to another level.
The feedback we received from the January conference has been very useful. In particular, we heeded the advice to put more technical content in our presentations and to create parallel "tracks", simultaneous presentations broadening the range of topics.
Early indications are that this was well received. We had our share of glitches with hardware, software and sound systems, probably less than we had a right to expect. No hugely embarrassing crashes, this will be for an even bigger occasion.
As we grow, I'd like us to continue with an effort to put on a professional show for our audience, but, at the same time, I wouldn't want to get too far on the side of gloss. It's a bad sign when programming manuals start looking like Cadillac brochures. The same goes for the venue. Our use of a weekend creates a burden, but we got great rates on everything, including airfare. I heard the hotel charged less than one third the rate it did the following week for the bigger company's event.
In many conferences, it is what takes place between sessions, the hallway schmoozing, that makes the value of the event. We haven't yet facilitated enough of this, it seems. It usually requires a resort, an off-site location where people stay for a couple of days. We would have to find creative ways to make it affordable without ending in a bad suburb of Paris.
In the same vein of encouraging two-way communication, the "plenary" Q&A session at the end will demand rethinking. A larger group will make it unwieldy and/or superficial. Those are great problems. And we need your help in addressing them in the right combination of professional and unconventional fashions. Venues, content, calendar, style, length, participants, guests, media, everything is open to re-examination. But for the central goal, creating the conditions for Be developers to succeed, and for us to rise with the tide as a result.
I will not leave the topic without expressing again my heartfelt thanks to Be developers who give meaning to our lives, and to my colleagues at Be for the best work I have ever been a part of.
Now, enough ecstasy, we have to deliver.