This chapter describes the DAT Audio Library, libdataudio, which you can use to process audio information stored on digital audio tape (DAT).
In this chapter:
“DAT Audio Library Basics” explains basic concepts for using libdataudio.
“Navigating through a DAT” explains getting locations from and seeking to locations on a DAT.
“Using the DAT Drive” explains how to use the DAT drive for playing and recording DATs, reading, writing and parsing DAT information, and communicating DAT status to the end user.
“DAT Sample Program” presents a DAT sample program.
The DAT Audio Library (libdataudio) supports processing the data from a digital audio tape (DAT). Because the device driver for the DAT drive is a standard IRIX tape device driver, the libdataudio library does not need the special positioning and status calls. Instead, you can use the standard open(), close(), read(), write(), and ioctl() system calls.
This section describes the basic concepts that underlie libdataudio. Because both CDs and DATs digitally encode an audio signal as a series of samples, the concepts and terms used when dealing with these media are similar; however, there are some differences between them.
A DAT contains 33.33 DAT frames per second of playing time. A DAT frame has both audio and nonaudio information. The sum of the nonaudio information in a DAT frame composes a single complete DAT subcode. When in audio mode and reading from a DAT, you need complete subcodes. Thus, in audio mode, a DAT frame is the smallest parcel of information you should read from a DAT.
To give you controlled access to either the audio data or the subcode in a DAT frame, libdataudio hands you a DTFRAME structure:
typedef struct dtframe {
char audio[DTDA_DATASIZE];
struct dtsubcode sc;
} DTFRAME;
|
A DAT audio sample is linearly encoded in a 16-bit two's-complement format. Because a complete stereo audio sample contains two interleaved channels, it takes four bytes of audio[] to contain a complete stereo audio sample (see Figure 9-1).
The byte ordering of audio sample frames in audio[] is based on the raw data from the DAT; its byte ordering is reversed from that on the IRIS workstation. DTDA_DATASIZE (the size of audio[]) is defined as 5760. This allows for 1440 audio sample frames per DAT frame, which, at 33.33 DAT frames per second, is enough to deal with audio sampled at rates of up to 48 kHz.
The subcode member uses a dtsubcode structure to contain the subcode read from the DAT. The subcodes contain information on sampling frequency, the number of channels, table of contents, catalog number, and more. For more information on the dtsubcode structure, see the DATFRAME(3) man page.
A DAT can have as many as 99 audio programs, each typically corresponding to a single song or musical piece. These programs are numbered 01 through 99. An audio program can have up to 99 subdivisions containing audio information. These subdivisions use the index numbers 01 through 99. Index number 00 is used for the pause between the audio programs.
A time code gives the hour, minute, second, and DAT frame offset into a DAT. When dealing with program time, the time code is a measure of the time elapsed since the start of the audio program. When dealing with absolute time, the time code measures the time elapsed since the start of the DAT. When dealing with run time, the time code measures the time elapsed since the beginning of the recording and contains several audio programs.
Accessing information from a DAT drive is analogous to reading information from a standard tape drive. To read a particular piece of information from the DAT, you must move to that location. The process of moving to a location on the DAT is known as seeking. Reading from the DAT is analogous to reading from a tape drive. You copy information from the device to a memory-resident buffer for further processing.
The parser lets your application change state in response to changes in the subcode data on a DAT. This lets you deal with the audio data in a way that is based on its content. To use the parser, you must give it callback routines that can deal with the subcode changes that interest you. Then you set up a loop that reads DAT frames from the DAT and calls the parser for each DAT frame.
The parser checks the subcode in every submitted DAT frame. If the parts of the subcode you care about have changed from the previous DAT frame, the parser executes one of your callbacks and hands it the new subcode information. Within your callback, you can examine the subcode information and change the state of your application as needed.
The DAT device driver is a standard IRIX device, so you can use the generic open(), close(), and ioctl() calls that you would use for any other tape device; however, unlike a standard tape drive, the DAT drive has an audio mode in addition to a straight data mode.
To put the DAT drive in audio mode, use ioctl() with MTIOCTOP and an mtop type structure, but set the mt_count member of the mtop structure to 1 before submitting that mtop structure to ioctl(). For example:
struct mtop mt_com; mt_com.mt_op = MTAUD; mt_com.mt_count = 1; /* 1 == audio mode, 0 == data mode */ ioctl(fd, MTIOCTOP, &mt_com); |
To move through a DAT tape, you use ioctl(), a standard IRIX system call. But before you can call ioctl(), you need to know where you are going. For most applications, destinations can come from either of two sources, the end user or calculations internal to your application.
Destinations from the end user come to your application in the form of ASCII strings. Destinations from internal calculations typically come in the form of a DAT frame count or, sometimes, as four values that specify the location in terms of hours, minutes, seconds, and DAT frames. Unfortunately, these forms are not suitable for seeking, so you must convert them before you can use them.
If your application wants to give end users the option of seeking to a DAT location defined in terms of time, your application can prompt the user for the time and then call DTatotime() to convert the string to a time code that you can submit to ioctl() for seeking.
Generally, the pure DAT frame count is the most convenient format to use when comparing two locations.
To convert to pure DAT frame counts, call:
to extract a pure DAT frame count from a dttimecode structure | |
to convert hours, minutes, seconds, and DAT frames to a pure DAT frame count | |
to convert an ASCII string to a pure DAT frame count |
You can then make your calculations and call DTframetotc() to convert the DAT frame count to a time code suitable for seeking.
It is also possible to make comparisons between locations expressed in terms of hours, minutes, seconds, and DAT frames. In that case, you can convert all locations into hours, minutes, seconds, DAT frames format by calling:
DTatohmsf() | to convert an ASCII string to hours, minutes, seconds, and DAT frames |
to convert a pure frame count to hours, minutes, seconds, and frames | |
to convert a time code to hours, minutes, seconds, and frames |
After making your calculations, convert the destination to a time code suitable for seeking by calling DThmsftoframe() followed by DTframetotc().
To seek to a location on a DAT, call ioctl() with MTSETAUDIO and an mtaudio type structure.
To specify the type of seek, set the seektype member of the mtaudio type structure to the appropriate MTAUDPOSN_* constant:
MTAUDPOSN_PROG | to seek to a program number |
MTAUDPOSN_ABS | to seek to an absolute time |
MTAUDPOSN_RUN | to seek to a running time |
MTAUDPOSN_PTIME | to seek to a program time (within program) |
To seek to a particular audio program on the DAT, set seektype to MTAUDPOSN_PROG, and use pno1, pno2, and pno3 members to pass in the three BCD numbers that identify the audio program you want. Program numbers range from 001 to 799. The pno1 member contains the most significant digit and pn3 contains the least significant digit. Thus, to seek to program 578, set the pn* members as follows:
struct mtaudio AudioProgNum; AudioProgNum.pn1 = 5; AudioProgNum.pn2 = 7; AudioProgNum.pn3 = 8; |
To seek to a location on the tape defined in terms of time, set the mtaudio seektype member to MTAUDPOSN_ABS, MTAUDPOSN_RUN, or MTAUDPOSN_PTIME and then specify the time location in the mtaudio members:
| atime | for MTAUDPOSN_ABS | |
| rtime | for MTAUDPOSN_RUN | |
| ptime | for MTAUDPOSN_PTIME |
These atime, rtime, and ptime members contain structures of type mtaudtimecode:
struct mtaudtimecode {
unchar hhi:4, hlo:4; /* hours */
unchar mhi:4, mlo:4; /* minutes */
unchar shi:4, slo:4; /* seconds */
unchar fhi:4, flo:4; /* DAT frame # */
};
|
The hhi and hlo members expect two digits that specify the hour to which you want to seek. The valid range for these two digits is from 00 to 99. The mhi and mhl expect the two digits that specify the minute to which you want to seek. The valid range for these two digits if from 00 to 59. The shi and slo expect the two digits that specify the second to which you want to seek. The valid range for these two digits is from 00 to 59. The fhi and fhl expect the two digits that specify the DAT frame to which you want to seek. The valid range for these two digits is from 00 to 33.
This section explains how to use the DAT Audio Library routines for:
playing a DAT
recording a DAT
reading and writing audio data from a DAT
parsing DAT information
communicating DAT status to the end user
Playing audio from a DAT is a little more complicated than playing a CD. For example, the sample rate for all CDs is 44.1 kHz; however, DAT audio may have been recorded at a sampling rate of 48 kHz, 44.1 kHz, or 32 kHz. Fortunately, a DAT records its sampling rate in the subcodes at the start of the tape, so you can read this sampling rate from the DAT before you must write DAT audio samples to the audio port.
In outline, a simple DAT-playing application must:
Define a callback routine for dt_sampfreq.
When the parser calls this routine, it passes in the frequency just read from the tape. Your callback should set a global variable to the frequency it gets from the parser. (See the DTaddcallback(3) man page.)
Define a callback routine for dt_audio.
When the parser calls this routine, it passes in the audio data from the DAT frame just parsed. The callback routine should write this data to the audio port using the sampling rate set by the dt_sampfreq callback.
Open the audio port.
Open the DAT drive.
Create a parser.
Add your dt_sampfreq and dt_audio callbacks to the parser.
Read samples from the DAT.
Parse the samples.
Write the samples to an audio port using the Audio Library.
When the application first starts reading the tape, it sees the frequency, calls your dt_sampfreq callback, and hands it the sampling frequency. As the parser continues to parse DAT frames, it also sees the audio data and executes your dt_audio callback for each new DAT frame containing audio.
For an example of a simple program that plays a tape in the DAT drive, see “DAT Sample Program”. For more information on using the audio port, see Chapter 6, “Programming with the Audio Library.”
When making recordings on DAT recorders that you want to play on a Silicon Graphics DAT drive, you must make sure you record at least one of the time codes. Most recorders will let you record audio without any time codes, so be certain you record the time codes. Record in standard mode; the DAT drive does not support long play (LP) mode or 4-channel (4CH) mode tapes.
To read audio data from the DAT drive, you need to open the DAT drive and put it in audio mode. Then you can call the standard IRIX read() system call as you would for any other tape device. The only complicating factor is that you need to ensure that you read complete DAT frames. This is not particularly difficult if you declare your receiving buffer to be an array of DTFRAME structures.
For example:
DTFRAME MyDATbuffer[4]; |
declares a buffer of four DTFRAME structures. If you then do a read such as:
n = read( MyDATtapeDevice, MyDATbuffer, sizeof(MyDATbuffer) ); |
you read in complete DAT frames and can easily access those complete DAT frames when you want to parse them.
To write audio data to the DAT drive, you need to open the DAT drive and put it in audio mode. Then you can call the standard IRIX write() system call as you would for any other tape device. Writing the tape is just a matter of writing DAT frames to the tape.
But setting the contents of the DAT frames is not just a matter of gathering together your audio samples. You must write subcode information that specifies things such as the sampling rate at which the audio was recorded. You must also update the DAT time code for each DAT frame that you write to the tape.
To help you set the subcode information for the DAT frames you want to write, libdataudio contains these routines:
| DTsetdate() | to set a date pack to the current time (useful for timestamps) | |
| DTinctime() | ||
| DTtcvalid() | to check that a time code is valid (use it after calling DTinctime()) |
For more information on the time code routines, see the appropriate man pages. For information on what you can write into the DAT frame subcodes, see the DTFRAME(4) man page. For additional information about properly writing DAT subcodes, see the DAT specification.
The DAT drive determines whether a tape is audio by looking for valid audio DAT frames. These frames must contain at least one valid time code field (absolute time, run time, or program time). When making recordings on DAT recorders that you want to later play on the Silicon Graphics DAT drive, you must make sure you record one of these time codes and that you record in standard mode.
The DAT specification requires that a tape begin with a special lead-in area of 100 DAT frames. Recording 100 frames ensures that the real recording will not begin over the plastic leader on the tape.
The following procedure provides the proper lead-in area:
create an empty DTFRAME
set the program number contained in the DAT frame to 0x0BB (beginning-of-tape, or BOT, code)
set the START bit in the control ID
set the subcode packs to 0x0AA (readable, not valid)
fill the audio data block with zeros
rewind the tape and repeatedly write the DAT frame at least 100 times
This section explains special precautions that must be taken when recording audio onto a tape that has previously been used as a data (DDS) tape.
When you insert a DDS tape into the DAT drive, it is rewound to the logical beginning-of-tape (BOT). On data tapes, the logical BOT differs from the physical BOT by approximately 10 centimeters (30 seconds). If you attempt to write data to the drive in audio mode, writing begins at the logical BOT. When you then rewind and play this tape, there is an initial 30-second gap before playback starts. If the tape is removed and then reinserted into a DAT drive, it is recognized as a data tape because DDS format data exists between the physical BOT and the DDS logical BOT.
Two sample programs are available to help you with DAT recording:
cdtodat.c , in /usr/people/4Dgifts/examples/dmedia/cd+dat
This program copies audio from a CD to a DAT. It contains example code for recording to DAT, including handling of the lead-in area and recording over data tapes.
verifydat.c , in /usr/people/4Dgifts/examples/dmedia/cd+dat
This program verifies that a DAT has been recorded correctly and has continuously running absolute time code.
After you have read in data from a DAT, you can start to process it. Typically, how you process the audio data depends on what its associated subcodes tell you about the data (for example, the sample rate at which the audio was recorded). If you want, you can directly examine the subcode associated with each DAT frame and respond appropriately.
The DTFRAME structure, however, is large and complicated and subject to change. libdataudio includes a parser so that your application can avoid dealing with the DTFRAME structure directly.
If you write a loop that passes all the read DAT frames through the parser, the parser can examine all the DAT frames for changes in the subcode. When the parser finds a change (seeing a subcode for the first time counts as a change), it executes the appropriate callback routine—depending on what sort of subcode change occurred—and passes the new subcode data into your callback routine.
The DAT parser distinguishes among 14 categories of subcode information. Thus, if you are interested in subcode changes for only one category of subcode data, the parser does not bother your application with subcode changes that you consider irrelevant.
To allocate and initialize the data structures for the DAT parser, you must call DTcreateparser().
To reset the parser after the user changes the tape in the DAT drive, call DTresetparser(). This clears out any information the parser has about the last DAT frame but leaves the callback routines in place.
When you define a callback for the parser, you must write a function of the form:
My_dat_SomethingCallBack( void* arg, DTDATATYPES type,
void* data)
{
/* your code here */
}
|
The parser uses the third parameter to pass in information it read from the subcodes. The parser uses the second parameter to pass in the type of callback it thinks it is calling. You can use this to assign the same function to different types of callbacks. Internally, you can switch on the type. This feature is useful if two callbacks are the same except for a few lines.
The parser does not use the first parameter. You can use that to pass in information if your application needs to call the callback directly.
To add callback routines to the parser, call DTaddcallback(). If you do not specify a callback for a category, the parser assumes you are not interested in changes of that type.
You can add callbacks that respond to changes in any of the following categories of subcode data:
| dt_audio | callbacks respond to changes in the audio data in a DAT frame. You can use this class of callback to notify you when you have gotten past the lead-in track and have started to see audio samples. When the parser calls this routine, it passes in the audio sample data. If this callback routine is a play routine for your application, it should write the audio sample to an audio port using the Audio Library. See the ALwritesamps(3) man page and Chapter 6, “Programming with the Audio Library.” | |||
| dt_pnum | callbacks respond to changes in the program number. You can use this callback to notice when you have moved from one program (track) to the next. | |||
| dt_index | callbacks respond to changes in the index number. You can use this callback to notice when you have moved from one subsection of a track to the next. | |||
| dt_ptime | callbacks respond to changes in the program time. You can use this callback to continuously update a “program time display” in a DAT-playing application. | |||
| dt_atime | callbacks respond to changes in the absolute time elapsed since the start of the DAT. You can use this callback to continuously update your application's information about total elapsed time. | |||
| dt_rtime | callbacks respond to changes in the run time elapsed since the start of a recording on the DAT. You can use this callback to continuously update your application's information about total elapsed time since the start of a recording. | |||
| dt_prortime | callbacks are like dt_rtime callbacks in that they respond to changes in the elapsed run time—however, the parser hands the callback more information than it gives to a dt_rtime callback—this type of callback is intended for professional uses | |||
| dt_mainid | callbacks respond to changes in the contents of the ID field | |||
| dt_sampfreq | callbacks respond to changes in the subcodes that describe the sampling frequency for the recording on the DAT | |||
| dt_toc | callbacks respond to changes in the subcode data that describe the table of contents for the tape
| |||
| dt_date | callbacks respond to changes in the timestamp for a recording | |||
| dt_catalog | callbacks respond to changes in the DAT catalog number | |||
| dt_ident | callbacks respond to changes in the ISRC identification number for the recording on the DAT | |||
| dt_probinary | callbacks respond to changes in the IEC (SMPTE) or Pro DIO time codes |
For more information on each subcode category, see the DTaddcallback(3) man page and the DAT specification.
To delete a callback, call DTremovecallback(). To change a callback, call DTremovecallback() followed by DTaddcallback().
Whether you get status information for the DAT directly from the DTFRAME structure or from one of your parser-callback routines, you need to convert that information to an ASCII string.
libdataudio includes these conversion routines:
For more information on these routines, see the relevant man pages.
This section contains datplay.c, a simple program for reading and processing DAT data.
Example 9-1 reads samples from the DAT and uses the parser and two callbacks to process the data read. One callback, frequency(), extracts the sampling rate from the subcodes on the DAT. The other callback, playaudio(), extracts audio samples from the frames and writes them to the audio port.
Example 9-1. Reading DAT Samples