[comp.sys.mac.programmer] System 7.0 Sound Manager Functionality

potts@itl.itd.umich.edu (Paul Potts) (04/19/91)

Hi all,

I've got a number of questions and concerns about the
Sound Manager. I'm currently working with System 7.0B6,
Think C with the revisions and header files from the
Beta 4 CD, a standalone version of Rez, and the .r files
from the Beta 4 CD as well.

My current project is a simulation of an audiometer, a
device used to test hearing. The device generates tones
at precise pitches and levels (750Hz at 60 dB, for
example). These pitches must be generated for an
arbitrary length of time, say, for as long as the
mouse is held down. Since this is a simulation, the
dB levels played through the headphones don't need to
be very accurate, but the pitches should be reasonably
so. The simulation is in Hypercard, but I have
experience writing XCMDs and intend to move the sound-
play functions to an XCMD.

For the prototype of this stack, I first used SoundEdit
to create samples of sine waves at various frequencies,
and saved them as tenth-of-a-second samples. I then hand-
edited the endpoints of the waves to eliminate, as much
as possible, any clicking, and play these sounds using
repeated calls to Hypercard's "play" function. The
results are surprisingly good. Some of the frequencies
generate clicks when played back-to-back like this, but
most of the lower ones don't. SoundEdit can only produce
these tones at volumes ranging from 0..100%, and I
would eventually like to have finer control than
that, but hey, the prototype works.

Now that it is time to turn the project from prototye
into a development version, I want to use the square-
wave synthesizer to generate these tones.

According to the documentation on the beta 1 CD
(until recently, all I had), it was possible to use
freqCmds and noteCmds (now called freqDurationCmds)
to play tones in two ways. The first was to pass a
value in param2 of the sound command in the range
1..127. The value was to be interpreted as a MIDI
note value. The second was to send the actual
frequency at which to play the note (as long as the
frequency was greater than 127).

This is the method I began attempting to use, and
wrote code to set up a sound channel and pass
the appropriate commands to it. I immediately began
to notice a problem. Values in the range 1..127 played
properly, but larger values didn't. Values between
127 and 255 seemed to map back to values in the range
1..127, producing strange tones. Values like 250, 500,
750, 1000, etc. - the exact tones I want my simulation
to reproduce - all produced the same extremely high
pitch.

After wrestling with my code for some time, and
determining that it was operating correctly, a colleague
and I dove into the system software code to follow the
frequency values, and see what happened to them. As we
suspected, the high bytes were getting stripped off, and
the values in the range 0..127 were being used to
calculate offsets from some base note such as middle C.
All this magic happens in one of the snth resources.

When I finally got access to the beta 4 CD/ROM and a
later draft of IM-6, I found that, in fact, the
documentation had been changed to reflect what was
actually going on. It seems that it is now only legal
to pass values in the range 1..127 to either freqCmds
or freqDurationCmds. Also, the SoundHeader data structure
has been slightly redefined. The field baseFrequency,
formerly a word, has been altered to two Byte fields.
The high byte is now used to select the encoding option,
and only a byte is left to select the base frequency.
This was altered to support different encoding options,
but perhaps a better method of extending the data
structure could have been found, one which didn't
require cutting the baseFrequency down to one byte.

Now I've got to look at other ways to play the sounds.
I have created a wave table out of a single sine wave,
but I can't use rateCmds with a waveTable, and have to 
use the MIDI values again. I can play the single wave
using the sampled sound synthesizer, but it can only be
played at note values, comes out too low. I will try
using rateCmd to play the wave at various frequencies,
but I'm not sanguine about my ability to use the base
frequency together with rateCmds to come up with appropriate
pitches.

Meanwhile, does anyone have ideas on a better way to do
this? I can't be the only one out there who needs to be
able to play sounds at arbitrary frequencies. Doesn't
anyone need to play notes at non-Western intervals?

I would attempt to do this myself using the older
Sound Driver calls, but they look very difficult, and
besides, they probably will not be well-supported in the
future.

If any Apple employees are reading, perhaps you have
a better perspective on the reasons that this important
functionality was removed from the Sound Manager. Since
this is my first project using the Sound Manager, I'm
not dead-certain that playing notes at arbitrary frequencies
ever worked (especially since the documentation I was
using was a draft). But it is something I would dearly
love to use.

Sorry to be so long-winded, and thanks for reading.

Please reply to me to potts@itl.itd.umich.edu.  If
I get interesting responses, I'll post them.

-Paul Potts-
Consultant, Office of Instructional Technology
Information Technology Division, University of Michigan
:x

blob@Apple.COM (Brian Bechtel) (04/23/91)

I asked the sound manager engineer, and he responds as follows:

potts@itl.itd.umich.edu (Paul Potts) writes:

> After wrestling with my code for some time, and determining that it
> was operating correctly, a colleague and I dove into the system
> software code to follow the frequency values, and see what happened
> to them. As we suspected, the high bytes were getting stripped off,
> and the values in the range 0..127 were being used to calculate
> offsets from some base note such as middle C.
 
> Meanwhile, does anyone have ideas on a better way to do this? I
> can't be the only one out there who needs to be able to play sounds
> at arbitrary frequencies. Doesn't anyone need to play notes at
> non-Western intervals?
 
> Please reply to me to potts@itl.itd.umich.edu.
> If I get interesting responses, I'll post them.
> -Paul Potts-
> Consultant, Office of Instructional Technology
> Information Technology Division, University of Michigan
 

Getting the frequencies that you want only requires some math and the
common Sound Manager routines.  Here's how to generate arbitrary
frequencies using sampled sounds.  You have to start with the original
sample rate and know the frequency that was sampled.  For example, if
you sampled a 440 Hz pitch at 11kHz and want it to be played at 460 Hz
you calculate a new sample rate.  Take the ratio of 460/440 as a fixed
point number.  Multiply this ratio with the original sample rate of
11kHz.  Put this number into the sound header's sample rate and use it
with a bufferCmd.  You'll play your sound back at 460 Hz.

There are some problems to look out for.  You have to be careful with
getting overflow in you equations.  Fixed point numbers have an upper
range of 65k, otherwise they become negative numbers.  So, some sample
rates and pitches cannot be created.  You will not be able to take a
22kHz sampled sound and play it back three octaves higher.  You could
use SANE to calculate the number with more precision, but you still
need to be careful with overflow.  Also, you cannot use SANE at
interrupt level.

There has NOT been any "important functionality" that was removed from
the Sound Manager.  Only frequency values of 0..127 are supported.
This is properly documented in Inside Mac VI.  Arbitrary frequencies
are not, and never been supported.  The freqCmd is exactly the same as
the freqDurationCmd but does not have an associated duration.  All
version of the Sound Manager, including what is shipping in System 7,
does not support anything other than values of 0..127.

The Sound Manager doesn't support this for a few reasons.  The Sound
Manager has no way of determining the original frequency of the sound.
A baseNote of 56 may be a pitch of 440 Hz, but what if the original
pitch wasn't in equal temperament (a pitch between the notes of a
piano)?  It's impossible to represent this in the Sound Header.

Additionally, to support frequencies we will need a new sound command.
The freqCmd was documented as having its high byte used for amplitude.
This only leaves three bytes to represent a frequency, but it should be
a Fixed type number.  Unfortunately this was not part of the original
plans for the new Sound Manager release and it was also not considered
important in the big picture.  Few developers have asked for the
feature.  Additionally, at the time we realized we needed a new command
for frequencies it was too late to add any new features.  Inside Mac
and the MPW interfaces was frozen, and we had an agreement with
developers not to change the API.

The Sound Header was changed when the original MACE package was
released.  This was done in 1989.  The MPW Sound interfaces have been
incorrect since then.  The former field "baseNote" which was a word
(with the high byte always being ignored) is now two fields.  This
change cause no problems for existing applications.  Applications being
compiled against the System 7 interfaces may have to be changed
slightly, with little effort.


Jim Reekes E.O., 
Macintosh Toolbox Engineering 
Sound Manager Engineer

ldo@waikato.ac.nz (Lawrence D'Oliveiro, Waikato University) (04/26/91)

I recently implemented an XCMD in HyperCard 2.0 that puts up a window
on which you can play notes. You hold down the mouse button anywhere
within the window, and it works out a pitch based on the position of
the pointer. While holding the button down, you can move the pointer
within the window, and the pitch changes accordingly. It even changes
smoothly, if you don't move the pointer too fast.

The way I did this is as follows.

* As soon as the button is pressed, I find my sound data and allocate
a sound channel.

* I feed a soundCmd command into the channel to specify the sound to be
played, followed by an ampCmd command to set the amplitude to zero, and
finally a freqCmd to start an indefinite-duration sound playing, at
the pitch of the sound's base note.

* I then use a getRateCmd to find the current relative sampling rate.
Note that some versions of the sound documentation indicate that getRateCmd
returns the current rate in param2; this is INCORRECT. Instead, you must
pass the address of a variable of type Fixed in param2, and this will
be set to the current rate.
  By the way, I send all my commands in this application with SndDoImmediate,
as I don't need any queueing, but this is probably unimportant.

+ I determine the current mouse position, and work out a new sample rate,
if I remember correctly as follows (noteNumber may have a fractional part,
and may be negative after the second line):

  	noteNumber := (mousePos.h - leftWindowEdge) / windowWidth * scale;
	noteNumber := noteNumber - baseNoteOfSound;
	newRate := originalRate * 2 ** (noteNumber / 12)

* I then set the new rate with a rateCmd, followed by an ampCmd to
change the amplitude to 255 (maximum), so the user actually hears the
sound playing.

* If the mouse position should change before the button is released,
I loop again from the step marked "+".

* When the user releases the mouse button, I send a quietCmd to
cut the sound, and deallocate the channel.

Lawrence D'Oliveiro                       fone: +64-71-562-889
Computer Services Dept                     fax: +64-71-384-066
University of Waikato            electric mail: ldo@waikato.ac.nz
Hamilton, New Zealand    37^ 47' 26" S, 175^ 19' 7" E, GMT+12:00
You are in a twisty little maze of Word 4.0 dialog boxes, all different.