Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
SoundApp.c
/* |
File: SoundApp.c |
Contains: 1.04: This was an update to support the Utilities Unit that MacDTS |
developed. Some of it was routines formally created for SoundApp, |
with additional ones coming from other sources. This helps to reduce |
the "destractive" code from the samples and makes them easier to maintain. |
1.1: ¥ This is the "new" SoundApp which adds some new features. |
¥ Some knowledge of the new Sound Manager is present |
in areas that were work-arounds for old Sound Manager bugs |
¥ Recording of sounds is now possible with the Sound Input Manager |
¥ The document window will keep the Record button hidden if |
the Sound Inpute Manager is not available for recording, |
otherwise it will expand the window size to show this button. |
¥ Supports copy and pasting of sound resources |
¥ Conversion to MPW 3.2 was established (with some amount of pain) |
¥ SoundApp now has its own documents |
¥ Supports launching by open documents from Finder |
¥ Added color icons for the System 7.0 desktop |
¥ Added the new Sound Manager error strings |
¥ DoErrorSound tests for safe beeps |
¥ Made Apple Legal happy by getting rid of the xxxxCmd |
and changed it to the freqDurationCmd. Also got rid of |
any other use of the work "xxxx" in a musical context |
1.2: ¥ Translated into C, some clean up along the way |
¥ Using universal headers |
¥ Gave up on the DTS utilities code, everything is self-contained |
Formatting was done with FONT = Courier, SIZE = 10, TABS = 4 |
many thanks to: Bo3b Johnson, Mark Bennett, Andy Shebanow, Keith |
Rollin, Chris Derossi, Pete Helme, Darin Adler, and my co-workers that |
sat near me while I was making lots of noise testing this application. |
To the reader, |
SoundApp.p is a sample application source file for demonstrating |
the Sound Manager. It requires the use of the SoundUnit to handle |
all of the sound routines. This portion of the source code handles the |
applicationÕs management of memory, errors, user interface, etc.. |
The read me notes are here in the source code because people tend to |
not read them otherwise. |
SoundApp is a demonstration of the Sound Manager released in System |
6.0.x. SoundApp is also a example of how to write a Macintosh |
application that performs good user interface and proper error |
handling. Believe it or not, but the Sound Manager portion was the |
easiest part for me to write. It was all the user interface and error |
handling that was the most difficult. One thing became very clear to |
me in the course of writing this application. The following axiom is |
one of the few great truths in the universe. |
If you write a Macintosh application without MacApp, youÕre working |
too hard. |
Throughout the development of SoundApp I would run into typical |
situations that make programming a Macintosh too hard. When this |
happened I asked myself Òwhat would MacApp do?Ó and that was followed |
by the thought Òthen why am I writing code that is already out there |
for me to use?Ó I started out intending on writing a very simple |
application that anyone could read, and understand the Sound Manager. |
I felt this meant not requiring the person to read Object Oriented |
Pascal. I accomplished part of my goal. People should be able to |
learn how to use the Sound Manager (in its present condition), but it |
didnÕt turn out to be such a simple application as I had hoped for. |
I have put a large amount of comments into the code. This is |
something IÕm really picky about. People do not comment their code |
enough. Each procedure has a paragraph that should explain what to |
expect that routine to do, and how it goes about doing it. There are |
some bigger issues that I will put into the release notes below. |
There are some things that make the Macintosh harder to develop for |
than it should be. Simple things should be simple. Too many things |
on the Mac that should be simple are not. Maybe someday these things |
will be fixed. |
¥ GetFontInfo requires a port set to the font in question. If I |
wanted to find the height of the System Font, I had to first set the |
current port to the WindowMgr. I could have used my own window, but |
what if I needed the font info before I had a window available? |
¥ The toolbox blows chunks when your heap gets Òtoo low.Ó I believe |
this magical number is between 32k and zero. The odds of blowing |
chunks increase logarithmically as one approaches 0 free bytes. |
¥ The Dialog Manager is not a free lunch and in fact will cost you |
plenty. |
¥ ThereÕs no safe way to determine how much memory opening a |
resource file will take away from your heap. |
¥ ThereÕs no way to determine how safe it is opening a resource that |
could be shared by other applications, especially on a local volume. |
¥ The Resource Manager doesnÕt always set ResErr. |
¥ The Sound Manager returns even less errors. |
¥ The List Manager returns no errors. |
¥ Writing a staggering routine for new windows encompasses a number |
of difficulties. How does one find the height of a windowÕs title |
region before the window is visible? |
Am I just a complainer? Do I have a bad attitude? Probably, but |
IÕm just trying to point out the above areas make the Mac programming |
experience difficult. These are areas that get developers into |
trouble. These areas need a sign in front of them that says, |
ÒDanger!Ó These are areas that developers get into and then write to |
MacDTS for help. |
Notation Conventions |
-------------------- |
All global variables begin with a lower case ÒgÓ. |
All constants begin with ÒkÓ except for those noted here. |
Resource IDs begin with ÒrÓ. |
Menu IDs being with ÒmÓ and items with an ÒiÓ. |
Resource strings begin with ÒsÓ. |
Human Interface |
--------------- |
This is the most important and about the most difficult aspect of |
programming on the Macintosh. In the development of SoundApp I gave much |
thought to the human interface issues. In fact, in talking with the Human |
Interface Group additions to the existing guidelines were made. The |
method of window stacking used here was a new one. This was documented in |
a Human Interface Tech Note. I even made one compromise (hard to |
believe!) suggested by the Human Interface Group. I originally had the |
buttons and the list in my choice of font and size. They felt that |
buttons should be in the System font and the list should also be the same. |
I liked my font choice better, but the group had a point that I really |
couldnÕt argue with. That was, ÒIf there isnÕt a compelling reason to |
change something standard, then donÕt change it.Ó Buttons on a Macintosh |
typically appear in the System font. Changing the font, just because I |
wanted to, was considered gratuitous. Standard File is in the System font |
and it also contains a list and buttons. Since my window are very similar |
to that dialog, IÕm using the System Font. |
SoundApp is never modal unless an error occurs and I need to show and |
alert. Controls are inactivated for inactive windows. The default button |
is given the proper outline, and this outline disappears when the window |
is deactivated. Keyboard equivalents for the buttons cause the button to |
appear as if it had been clicked in. The check box in the Standard File |
dialog remembers the userÕs last setting. The about box is only |
semi-modal. It will allow the user access to switch to another application. |
The status window under some circumstances was found to disappear too quickly, |
so a built in delay was added. Windows are centered or stacked according |
to the Human Interface Guidelines. The sound level isnÕt adjusted by the |
application, and instead the users is informed of the current level and |
told how it can be adjusted. |
The About box |
------------- |
ItÕs rad. Has a color icon, shows the 'vers' resource, goes Moof!ª. It |
also demonstrates how to handle a modal window without the Dialog Manager. |
This technique can be use for any window, including dialog windows. The |
dialogÕs update routine would call UpdtDialog. The really new point to |
notice is this window is modal but ONLY within the applicationÕs layer. |
While running under MultiFinder, the user can switch to other |
applications. While the About window is present, it is the only window |
belonging to the application that accepts user actions. |
Memory Management |
---------------- |
This has to be the most difficult portion of a Mac application to |
write. This along with the user interface. I spent too many nights |
chasing down crashes while running the application under low memory. |
I found unpleasant surprises while doing this. The Sound Manager |
doesnÕt check for NIL pointers. OpenResFile may take large portions |
of my heap away. The toolbox seems to need at least 32k of free space |
in the heap of my Mac II running color. |
I wrote a simple grow zone procedure that will dump a reserve memory |
block. This is only considered for use in an emergency. I never rely |
on using it directly. If the reserve has been released, I will not |
continue an operation such as playing a sound or showing the status |
window until it is regained. Grow zones should not be considered a |
solution to memory management. They can be used to augment your |
overall memory management scheme. |
Error Checking |
-------------- |
Lots and lots and lots of it. I could even do more. Programmers |
need to do more of this. The Sound Manager will crash when |
encountering a NIL pointer. My application should never crash. If |
you can find a way to crash this application, then I want to hear |
about it. Using a bogus 'snd ' resources doesnÕt count and IÕve found |
many of those. Writing proper error checking into the code during |
development really helped. Always handle errors, and pass along the |
error. I will let the first error encountered to be passed all the |
way up to the caller and eventually my error dialog will show up for |
the user. |
SetPort Strategy |
---------------- |
Any routine that needs to use Quickdraw will set the port. I do not |
believe that it should also be responsible for restoring the port back |
to what it may have been before the routine was called. So, youÕll |
find there is an absence of the GetPort, SetPort, do my thing, and |
then SetPort again. Instead I SetPort and do my thing. The Mac often |
is setting the port unnecessarily. |
Strings |
------- |
All of my strings are resources. There are no strings that appear |
within the code. This helps memory management and allows me to adjust |
the application to international systems without compiling any code. |
I could simply use ResEdit, or some other tool, to localize this |
application. I find it is just as easy to run Rez again than |
attempting to use ResEdit. Besides, after editing with ResEdit I want |
the source for that and would have to run DeRez which isnÕt nearly as |
clean as my original source files. |
Window Stacking |
--------------- |
I hate applications that will open a new document that covers up an |
existing document, unless the new document covers the entire screen. |
So, my applicationÕs documents have a small window size. I wanted to |
open new windows that would not cover up older ones. This is nice for |
the user, since they will not have to move windows just to get at |
other documents. ResEdit will stagger new windows off of the |
frontmost window but I find that this isnÕt the best approach. It |
will still cover up other windows and I also donÕt like windows that |
will open half way between two monitors. I wanted a better approach: |
one that would always stagger new windows and not cover up older ones. |
When I want to center a window, I need to know its entire rectangle |
size. The rub is that I cannot determine its size until I show it |
because I only know about the windowÕs boundsRect. This does not |
include the area that contains the title bar. ThatÕs the strucRgn, |
which is an empty region for an invisible window. I could do what |
MacApp does, but if I have to do another thing that MacApp already |
does IÕll give up and stick with MacApp. I ended up writing a routine |
that takes a guess at the height of the windowÕs title bar. This is |
another thing that was harder than it should have been. |
Dialog Manager (and some of the reasons I donÕt like it) |
-------------------------------------------------------- |
My first approach was to use modeless dialogs for document windows, |
thinking that I could write an application that would demonstrate how to |
deal with them and all of their idiosyncrasies. Not long into the |
development cycle it became obvious to me that I was fighting something |
that contained more disadvantages than advantages. I removed all the |
dialog code and only used standard windows and controls the old fashion |
way. In the case of the About window, which is semi-modal, I have a test |
that will return TRUE for a window that should be treated as modal. This |
allows my window to be handled by my standard event handlers and I donÕt |
have to write dialog filters. There are some things that do not get |
handled properly while calling ModalDialog. ModalDialog ignores disk |
insert events. The activate or update events do not get handled for |
background windows. Using a modeless dialog fails with MultiFinder if |
switching takes place while the dialog is the frontmost window. The |
problem is that DialogSelect ignores and removes the suspend/resume event. |
Another advantage to all this is that drawing was much faster. |
As an example of some of the problems with ModalDialog and the activate |
event. Try this with the Finder. Open a window and choose ÒView by name.Ó |
Then select a few names with the shift key and resize the window so the |
vertical scroll bar is visible. Move this window to one edge of the |
screen or a second monitor. Now choose ÒSet Startup.Ó This is a modal |
dialog. If you look at the Finder window with the selected files, youÕll |
notice that the scrollbar and the text are still highlited. This is not |
the proper user interface. This is because the deactivate routines are |
not called while in ModalDialog. You can even find this problem with |
SoundApp. On deactivate events I will change my controls to the inactive |
state. If you place the buttons to the side of the screen and then bring |
up the standard file dialog, youÕll notice that the buttons donÕt change |
properly. ModalDialog also prevents the application from updating |
background windows too. To solve this a dialog filter procedure is |
required. In most cases, this filter would be as complex and the event |
loop. It would also make it necessary to call your event routines from |
outside of the normal event loop. All on this isnÕt worth the effort. |
You can see how this does not happen while using this applicationÕs |
About window. Select an item in the document window and choose ÒPlay |
Melody.Ó This will leave the status window on screen so that you can |
drag it to cover the document window. Now select ÒAbout SoundAppÓ to |
bring up the about window. This causes the status window to close, |
which uncovers the document window leaving an invalid area. The |
document window gets an activate event, then the About window appears. |
Then the document window is properly deactivated and updated. Yeah, |
just like it should happen. |
So, the tradeoff was that I didnÕt have to work around all the |
strange things the Dialog Manager does such as running a secondary |
event loop, and requiring me to have userItems or filterProcs. This |
made the code smaller, more readable, and faster. I think I will |
avoid the Dialog Manger from now one unless IÕm using a very simple |
dialog. The about window of this application proved too much for the |
Dialog Manager. |
One thing dialogs are good for is running ResEdit and laying out the |
dialog. To help position controls, I used a DLOG resource of the same |
size as my WIND resource. The DITL of this dialog contains the |
positions I wanted for my CNTL resources. This helped me to look at |
where I could expect my buttons to show up. This is one of the main |
reasons people think they need the Dialog Manager, because ResEdit |
makes it easy to build dialogs. ResEdit alone has contributed to |
nearly all of the Dialog Manager abuse in the world today. |
I used a Rect resource for positioning the list rectangle of the |
document windows. These windows look very much like a modeless |
dialog. (They used to be, but that presented to many problems.) The |
About window is also a standard window, but shown modally. Just like |
ModalDialog, but my modal window does allow switching under |
MultiFinder. You can change the window to a dBoxProc and then |
MultiFinder will not switch while this is the active window. To help |
with the layout of the about window, I position the text within it |
based on the size of the window. The status window does this too. |
These two things, the Rect resource and text based on the size of the |
window, help when changing the text. If the new text doesnÕt fit, |
then resize the windowÕs resource. I used some trick with Rez to help |
layout my window contents. Refer to the SoundApp.r sources. |
IÕve read and understood Tech Note 203, and have learned how to |
apply it. Bo3b Johnson is a smart guy, and developers should trust |
his opinions. |
List Manager |
------------ |
ItÕs very easy to be tempted by this part of the toolbox, along with |
the Dialog Manager. The List Manager is a slow beast at times. It |
also has some problems with Òdoing the right thing.Ó IÕve found that |
the list will not be updated properly when the user clicks in a cell |
that is out of bounds. LClick will return TRUE with a cell that |
doesnÕt exists. LActivate will erase the scrollbars instead of |
highlighting the properly. Finally, the List Manager does not return |
errors. How would a person know if LSetCell worked? |
IÕve read and understood Tech Note 203, and have learned how to |
apply it. |
Resource Manager |
---------------- |
I test all the handles being returned from the Resource Manager |
before using them, and if I get a NIL then I look at ResError. |
ResError sometimes lies and returns noErr and a NIL handle. ResError |
is usually good for getting an error code AFTER youÕve already found |
an error. |
Opening a resource file that is already open by another application |
is dangerous. The Resource Manager will not tell you when youÕve done |
this. There needs to be a OpenRFPerm that will return permission |
errors such as resFileBusyErr. Refer to Tech Note 185. |
When I or the Toolbox needs to get at one of my resources, |
CurResFile must be set to my application. Also, look out for one |
particularly nasty situation when switching resource files. If the |
segment loader goes for a CODE segment, it better be from our resource |
file! The idea here is, in case you didnÕt get it already, always |
have the current resource file be set to the application. If a |
resource is needed from another file, switch momentarily to get the |
resource and immediately restore the current resource file to the |
application. I take an added measure of defense and whenever I need a |
resource I use the Get1Resource calls. These will only search the |
current resource file. |
Strategies For Sound |
-------------------- |
All of the Sound Manager code is contained in the SoundUnit.p. This |
code was written to be general purpose, providing useful routines for |
other applications. Lots of error checking is performed. IÕve also |
extended the support for SndPlay and made it really asynchronous. |
IÕve demonstrated most of the abilities the present Sound Manager has |
to offer. I will have to revise the SoundAppUnit to include any new |
features (e.g., multi channel support) when the next Sound Manager is |
released. |
I allocate my own memory to be used as sound channels. I allocate |
these pointers early in the applicationÕs startup time to avoid memory |
fragmentation. These channels are of the standard size (holding 128 |
commands) but IÕve extended the structure to include my own |
information. When I create a new sound channel, I pass it a pointer |
to this memory. This will link in the 'snth' resource and hardware to |
my channel. When I dispose of the channel, the Sound Manager will |
purge this resource and disconnect me from the hardware. When adding |
the 'snth' resource, the Sound Manager will allocate a pointer into |
the applicationÕs heap instead of the systemÕs. This is a modifier |
stub used by the 'snth'. This could cause some problems with memory |
management. I create and dispose of all my channels as soon as |
possible, and this doesnÕt cause me problems. |
I keep track of which document is playing a sound, along with a |
global of when the application is playing sound. I needed to keep |
track of which document is playing because if the user disposes of |
that document, I will have to stop playing the sound contained in it |
since the user wants to dispose of that data. I keep track of when |
the application is playing sound in a global. This is only used by |
the routine that calculates the sleep time for WaitNextEvent. |
I came up with a pretty sick music notational system using Rez. |
Refer to the notes in the SoundAppSnds.r file. If youÕve just |
finished a meal, wait four hours before reading. |
The SoundUnit handles all of the Sound Manager code entirely. This |
eliminates any and all references to the Sound Manager from the |
application. The SoundUnit will return any error encountered while |
calling the Sound Manager, and does some extra error checking the |
Sound Manager doesnÕt do. |
The portion of the application that uses the wave table synthesizer |
is more complex than the other two. I wanted to include an example |
channel modifier for use in the wave table channels. This would have |
been a transpositional modifier that would take a given freqDurationCmd and |
transpose it by some amount. This would be nice for the routine that |
plays a scale, by allowing the other three channels to be playing the |
same scale but at a different interval. Unfortunately, I found that |
the Sound Manager has bugs using a modifier, at least with the wave |
table synths, and could not use them. |
IÕve created a few wave table sounds and keep them in a 'snd ' |
resource. This allows me to change the sound of the wave table |
channels and not change any of the code. Creating wave table data is |
complicated. The example sounds IÕve included are samples IÕve taken |
from various sources. IÕve cleaned them up quit a bit. This was to |
set loop points, try and reduce clicks, correct the sample rates, and |
base frequencies. This is also a complicated task. Maybe I should document |
these techniques. |
Written by: Jim Reekes |
Copyright: Copyright © 1994-1999 by Apple Computer, Inc., All Rights Reserved. |
You may incorporate this Apple sample source code into your program(s) without |
restriction. This Apple sample source code has been provided "AS IS" and the |
responsibility for its operation is yours. You are not permitted to redistribute |
this Apple sample source code as "Apple sample source code" after having made |
changes. If you're going to re-distribute the source, we require that you make |
it clear in the source that the code was descended from Apple sample source |
code, but that you've made changes. |
Change History (most recent first): |
7/29/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1 |
*/ |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// global defines |
// All of this code is for System 7 and later |
#define SystemSevenOrLater 1 |
// I don't use any obsolete stuff in the interfaces, and you shouldn't either. |
// You also need to worry about names because the Code Fragment Manager binds |
// system calls by name. Read the comments in ConditionalMacros.h |
#define OLDROUTINENAMES 0 |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// includes |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
#include <FixMath.h> |
#include <AppleEvents.h> |
#include <Controls.h> |
#include <Devices.h> |
#include <DiskInit.h> |
#include <Dialogs.h> |
#include <Errors.h> |
#include <Events.h> |
#include <Files.h> |
#include <Icons.h> |
#include <Gestalt.h> |
#include <Lists.h> |
#include <LowMem.h> |
#include <Menus.h> |
#include <Memory.h> |
#include <QuickDraw.h> |
#include <Resources.h> |
#include <Scrap.h> |
#include <Script.h> |
#include <SegLoad.h> |
#include <StandardFile.h> |
#include <TextUtils.h> |
#include <ToolUtils.h> |
#include <Traps.h> |
#include <Types.h> |
#include <Windows.h> |
#include <limits.h> |
#include <string.h> |
#include <Sound.h> |
#include <SoundInput.h> |
#include "SoundUnit.h" |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// constants |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
enum { |
kOSTrapBit = (1<<11), /* bit 11 is the OS Trap bit */ |
kTrapNumberMask = 0x07FF, /* bits used by the A-Trap mechanism */ |
kPollingSleepTime = 60, //MultiFinderÕs sleep while sound is playing |
kNumberOfMasters = 3, //number of master pointer blocks we expect |
kSizeOfReserve = (unsigned long)32 * 1024, //size of reserve memory for grow zone proc |
kMinSpace = (unsigned long)32 * 1024, //minimum available memory I allow in heap |
kMemForSndDoc = 20 * 1024, //minimal amount of memory needed by document |
rAppSignature = 'SAPP', //applicaitonÕs OS signature |
rSndAppDocType = 'sDoc', //document's file type |
kScrollbarAdjust = 16-1, //the width of the scrollbar in the list |
kListFrameInset = -1, //inset rectangle adjustment for list frame |
kStadardWhiteSpacing = 13, //inset rectangle adjustment for dialog items |
kRecordTop = 40, //40,50 is topLeft for SndRecord dialog |
kRecordLeft = 50, //40,50 is topLeft for SndRecord dialog |
kHiliteControlSelect = 1, //select the control |
kHiliteControlDeselect = 0, //deselect the control |
kCntlOn = 1, //controlÕs value when truned on |
kCntlOff = 0, //controlÕs value when truned off |
kWindowPosStartPt = 2, //offset from the topLeft of the screen |
kWindowPosStaggerH = 16, //staggering amounts for new windows |
kWindowPosStaggerV = 3, //not including the windowÕs title height |
kMinWindowTitleHeight = 19, |
kButtonFrameSize = 3, //buttonÕs frame pen size |
kButtonFrameInset = -4, //inset rectangle adjustment around button |
kButtonSizeH = 17, //standard height of buttons |
kDafaultButSizeH = kButtonSizeH - kButtonFrameInset - kButtonFrameInset |
}; |
//refer to the SoundApp.r file for the explaination about these numbers |
enum { |
kNumOfButtons = 6, //the number of buttons in the window |
kSndButtonSizeW = 100, //SizeW of buttons in document window |
kSndButtonSizeH = 22, //heigth of buttons in document window |
kSoundWindowSizeH = ((kNumOfButtons * (kStadardWhiteSpacing + kSndButtonSizeH)) |
+ kStadardWhiteSpacing), |
kSoundWindowSizeW = 296, |
//standard position for icons in alerts |
kIconWidthOrHeight = 32, |
kStdAlertIconTop = kStadardWhiteSpacing, |
kStdAlertIconLeft = 23, |
kStdAlertIconBottom = kStdAlertIconTop + kIconWidthOrHeight, |
kStdAlertIconRight = kStdAlertIconLeft + kIconWidthOrHeight, |
kFSAsynch = true, //asynchronous File Manager call |
kEnterKey = '\3', //the keys IÕm looking for |
#ifdef applec |
kReturnKey = '\n', //MPW C is wrong, the return key is "\r" not "\n" |
#else |
kReturnKey = '\r', |
#endif |
kEscape = '\33', |
kUpArrow = '\36', |
kDownArrow = '\37', |
kPeriod = '.', |
kBackspace = '\b', |
//This bit set in the ioFlAttrib field if the fileÕs resource fork is open. |
kResForkOpenBit = (1 << 2), |
//For the delay time when flashing the menubar and highlighting a button. |
kDelayTime = 8, //8/60ths of a second |
//The minimal number of ticks the ShowWindow needs to be visible. |
kShowTimeDelay = 20, |
//BUG NOTE: timbreCmd value of 255 on the Mac Plus/SE will cause a crash Sound Manager 1 |
kSquareWave = 240, //square wave for squareWaveSynth |
kSineWave = 0, //sine wave for the squareWaveSynth |
kPreferredTimbre = 190, //my preferred timbre for the squareWaveSynth |
//Application snd resources must be higher than this number. |
kSystemSndRange = 8191 //This is the highest snd id reserved by Apple. |
}; |
//The following constants are the resource IDs. |
enum { |
rMenuBar = 1000, //applicationÕs menu bar |
rExitAlert = 1000, //emergency exit user alert |
rUserAlert = 1001, //error message user alert |
rSoundVolAlert = 1002, //sound is set low alert |
rSaveAlert = 1003, //save changes? dialog |
rGetNameDLOG = 2000, //get a name for the sound dialog |
rNameItem = 3, //edit text item in rGetNameDLOG |
rUserItem = 5, //user item to help draw default outline |
rCustomGetFileDLOG = 2001, //dialog template for CustomGetFile |
rSndOnlyCheckBox = 10 //dialog item number in CustomGetFile |
}; |
/* |
These are the window IDs used in the applications. Each one must be unique, |
since they are used to identify which window the event took place in. |
*/ |
enum{ |
rAboutWindow = 1000, //about window |
rStatusWindow = 1001, //sound status window |
rSoundWindow = 1002, //sound document window |
rListRectID = 1000, //resource containing size of list rectangle |
rCancelCntl = 1000, //stop button ID for the status window |
rPlaySndCntl = 1001, //sound button IDs for the sound document window |
rHyperPlayCntl = 1002, |
rPlayScaleCntl = 1003, |
rMelodyCntl = 1004, |
rStopCntl = 1005, |
rRecordCntl = 1006, |
rAboutOkCntl = 1007, |
rUntitled = 1000, //string ID for untitled resources |
rAboutText = 1001, //string ID for text appearing in about window |
rPutFileMsg = 1002, //string for text appearing in SFPutFile dialog |
rMoofIcon = 1000, //resource ID for ICON of application |
rAppPict = 1000, //resource ID of picture shown in about window |
rSndCursor = 1000 //cursor resource for our documents |
}; |
/* |
The following are the snd resource IDs contained in the application, |
which start at ID 9000. IDs 0 - 8191 are reserved for Apple. |
*/ |
enum { |
rMoofSound = 9000, //snd for the about window |
rScaleSnd = 9001, //snd containing a scale |
rMelodyPart1 = 9002, //snd containing a melody |
rMelodyPart2 = 9003, //snd containing the harmony |
rMelodyPart3 = 9004, //snd containing the harmony |
rMelodyPart4 = 9005, //snd containing the harmony |
rWaveHarmony = 9006, //snd containing waveTable for harmony |
rWaveMelody = 9007, //snd containing waveTable for melody |
rCounterPt1 = 9008, //snd containing soprano part of counter point |
rCounterPt2 = 9009, //snd containing alto part of counter point |
rCounterPt3 = 9010, //snd containing tenor part of counter point |
rCounterPt4 = 9011, //snd containing bass part of counter point |
rSopranoVox = 9012, //snd containing waveTable of soprano voice |
rAltoVox = 9013, //snd containing waveTable of alto voice |
rTenorVox = 9014, //snd containing waveTable of tenor voice |
rBassVox = 9015, //snd containing waveTable of bass voice |
rSampleHarmony = 9016, //snd containing the sampled sound harmony |
rSampleMelody = 9017 //snd containing the sampled sound melody |
}; |
//The following are resource IDs for messages. |
enum { |
sErrStrings = 1000, //error string STR# ID |
sStandardErr = 1, //An error has occurred. |
sMemErr = 2, //A Memory Manager error has occurred. |
sResErr = 3, //A Resource Manager error has occurred. |
sCurInUseErr = 4, //That file is currently in use. |
sWavesBroken = 5, //The wave table synthesizer is not available. |
sWrongVersion = 6, //This system does not support the Sound Manager... |
sLowMemory = 7, //Memory is too low to continue... |
sNoMenus = 8, //Could not find applicationÕs menu resources. |
sInitSoundErr = 9, //Could not initialize the SoundUnit. |
sSoundErr = 10, //The Sound Manager has encountered an error. |
sNewDocErr = 11, //Could not create a new document. |
sInitStatusErr = 12, //Error initializing the status window. |
sEditErr = 13, //Could not complete the edit command. |
sDocErr = 14, //There is a problem with this document. |
sMsgStrings = 1001, //message string STR# ID |
sPlayingMsg = 1, //playing a sound |
sHyperMsg = 2, //playing a sound the Hyper way |
sScaleMsg = 3, //playing scale |
sMelodyMsg = 4, //playing melody |
sTimbresMsg = 5, //playing various timbres |
sCounterPtMsg = 6, //playing 4 part counter point |
sSMErrStrings = 1002 //strings describing Sound Manager errors |
}; |
/* |
The following constants are used to identify menus and items. The menu IDs |
have an ÒmÓ prefix and the item numbers within each menu have an ÒiÓ prefix. |
*/ |
enum { |
mApple = 128, //Apple menu and items |
iAbout = 1, |
mFile = 129, //File menu and items |
iNew = 1, |
iOpen = 2, |
iClose = 4, |
iQuit = 12, |
mEdit = 130, //Edit menu and items |
iUndo = 1, |
iCut = 3, |
iCopy = 4, |
iPaste = 5, |
iClear = 6, |
mDemos = 1000, //Demos menu and items |
iCheckVolume = 1, |
iSquareScale = 3, |
iSquareMelody = 4, |
iSquareTimbre = 5, |
iWaveScale = 7, |
iWaveMelody = 8, |
iWaveSATB = 9, |
iSampleMelody = 11, |
iSampleSATB = 12 |
}; |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// macros |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Define HighWord and LowWord macros for efficiency. |
#define HighWord(aLong) ((aLong) >> 16) |
#define LowWord(aLong) ((aLong) & 0x0000FFFF) |
// accessing a rectangle's points |
#define TopLeft(r) (* (Point *) &(r).top) |
#define BottomRight(r) (* (Point *) &(r).bottom) |
#define AbsoluteValue(n) ((n > 0) ? n : -n) |
#if GENERATINGCFM |
#define CreateRoutineDescriptor(info, proc) \ |
RoutineDescriptor g##proc##RD = BUILD_ROUTINE_DESCRIPTOR(info, proc) |
#define GetRoutineAddress(proc) (&g##proc##RD) |
#else |
#define GetRoutineAddress(proc) proc |
#endif |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// types |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
typedef RectPtr *RectHandle; |
/* |
This is a document layout that contains the window record and references |
to the associating data. The window record MUST be the first field. This |
is because I use the window pointer returned by the Toolbox to be a |
pointer to my document. To confirm that the window pointer is a document |
pointer, I store an application reference in the window recordÕs refCon. |
Then, I use a routine to test for the presence of this reference to insure |
IÕm looking at one of my documentÕs windows. |
*/ |
struct SndDocument { |
WindowRecord window; // must be first field |
short resFile; // documentÕs resource file |
short vRefNum; |
long dirID; |
ListHandle list; // documentÕs list of sounds |
Boolean sndInUse; // document is using a 'snd ' resource |
}; |
typedef struct SndDocument SndDocument; |
typedef SndDocument *SndDocPeek; // to peek at the document record |
// This is the status window layout. The concept here is similar to the |
// document type mentioned above. The message is a string handle used to |
// store the current message. |
struct StatusWindow { |
WindowRecord window; |
StringHandle message; // current text of status message |
long showTime; // time window was shown |
}; |
typedef struct StatusWindow StatusWindow; |
typedef StatusWindow *StatWindowPeek; |
// This is the about window layout. The concept here is similar to the |
// document type mentioned above. The comment is a string handle used to |
// store the current message. |
struct AboutWindow { |
WindowRecord window; |
Handle appPict; // handle to picture of appÕs name |
Handle comment; // handle to string of about comments |
}; |
typedef struct AboutWindow AboutWindow; |
typedef AboutWindow *AboutWPeek; |
// This is the template to the WIND resource. I used it to load in the WIND |
// resource and then adjust the boundsRect. I also look at the procID to |
// determine if it has a title bar or drag region. |
struct WindowTemplate { // template to a WIND resource |
Rect boundsRect; |
short procID; |
Boolean visible; |
Boolean filler1; |
Boolean goAwayFlag; |
Boolean filler2; |
long refCon; |
Str255 title; |
}; |
typedef struct WindowTemplate WindowTemplate; |
typedef WindowTemplate *WindowTPtr, **WindowTHndl; |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// inlines |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is a handy routine to copy pascal strings. It's smaller than the |
library call pstrcopy, and faster too. |
*/ |
#if GENERATING68K |
#pragma parameter PStringCopy(__A0,__A1) |
void PStringCopy(StringPtr source, StringPtr destination) |
FIVEWORDINLINE (0x7000, 0x1010, 0x12D8, 0x51C8, 0xFFFC); |
// moveq #0,d0 |
// move.b (a0),d0 |
// loop move.b (a0)+,(a1)+ |
// dbra d0,loop |
#else |
#include <DriverServices.h> |
#define PStringCopy(source, dest) PStrCopy(dest, source) |
#endif |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// prototypes |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
short NumToolboxTraps(void); |
Boolean TrapExists(short theTrap); |
pascal long MyGrowZone(Size cbNeeded); |
Boolean LowOnReserve(void); |
void RecoverReserve(void); |
Boolean AllocateReserve(void); |
Boolean FailLowMemory(long memRequest); |
Boolean HasSelection(SndDocPeek sndDoc); |
OSErr GetSelection(SndDocPeek sndDoc, SndListHandle *sndHandle); |
void SelectNextCell(ListHandle list, Boolean next); |
void SelectSndCell(SndDocPeek sndDoc, short resID); |
OSErr InitSndList(SndDocPeek docPtr); |
Boolean OpenByApp(FSSpecPtr file, WindowPtr *window); |
Point GetGlobalMouse(void); |
short GetWTitleHeight(short variant); |
Point GetGlobalTopLeft(WindowPtr window); |
void MyParamText(StringHandle text, StringPtr cite0, StringPtr cite1); |
void CenterWindowRect(short variant, Rect *theRect); |
WindowPtr GetCenteredWindow(short id, Ptr p, WindowPtr behind); |
short CenteredAlert(short alertID); |
DialogPtr GetCenteredDialog(short id, Ptr p, WindowPtr behind); |
void DoButtonOutline(ControlHandle button); |
void SelectButton(ControlHandle button); |
void ActivateSndCntls(SndDocPeek sndDoc); |
Boolean IsDAWindow(WindowPtr window); |
Boolean IsDocWindow(WindowPtr window); |
Boolean IsModalWindow(WindowPtr window); |
void KillSound(void); |
Boolean DoCloseWindow(WindowPtr window); |
void AlertUser(short error, short messageID); |
void EmergencyExit(short message); |
void Terminate(void); |
void DrawStatusWindow(void); |
void ShowStatusWindow(short messageID); |
void InitStatusWindow(void); |
pascal void DoErrorSound(short soundNo); |
void CheckSoundVolume(void); |
void AddSndDocControls(WindowPtr window); |
Boolean PositionAvailable(Point newPt, short wTitleHeight); |
WindowPtr NewStackedWindow(short windID, Ptr windStorage); |
OSErr CreateSoundDoc(short resRef, FSSpecPtr file); |
void OpenSoundDoc(FSSpecPtr file); |
void NewSoundDoc(void); |
pascal Boolean SFFilter(CInfoPBPtr p, Boolean *sndFilesOnly); |
pascal short SFGetHook(short mySFItem, DialogPtr dialog, void *sndFilesOnly); |
void GetSoundDoc(void); |
void DrawAboutWindow(WindowPtr window); |
void DoAbout(void); |
void PlaySelectedSnd(SndDocPeek sndDoc, short message); |
void PlaySndSong(SndDocPeek sndDoc, short sndID); |
void PlaySquareSong(short sndID); |
void PlaySquareTimbres(void); |
void PlayWaveScale(void); |
OSErr GetSndSongs(short sndSongID1, short sndSongID2, short sndSongID3, short sndSongID4, SndListHandle *sndSong1, SndListHandle *sndSong2, SndListHandle *sndSong3, SndListHandle *sndSong4); |
OSErr InstallWaveSnds(SndChannelPtr chan1, SndChannelPtr chan2, SndChannelPtr chan3, SndChannelPtr chan4, short waveID1, short waveID2, short waveID3, short waveID4); |
void PlayWaveMelody(void); |
void PlayWaveSATB(void); |
void PlaySampleMelody(void); |
void PlaySampleSATB(void); |
pascal void DefaultOutline(WindowPtr window, short theItem); |
void GetSndName(Str255 sndName); |
OSErr AddSnd(SndDocPeek sndDoc, StringPtr sndNamePtr, SndListHandle sndHndl); |
void ClearSnd(SndDocPeek sndDoc); |
void DoRecordSound(SndDocPeek sndDoc); |
void AdjustMenus(void); |
void CopySnd(SndDocPeek sndDoc); |
void CutSnd(SndDocPeek sndDoc); |
void PasteSnd(SndDocPeek sndDoc); |
void DoMenuCommand(long menuResult); |
void DrawSndWindow(WindowPtr window); |
void DoKeyDown(char key, WindowPtr window); |
Boolean ListClick(SndDocPeek sndDoc, EventRecord *event); |
void DoSndDocClick(SndDocPeek sndDoc, EventRecord *event); |
void DoStatClick(StatWindowPeek statWindow, EventRecord *event); |
void DoAboutClick(WindowPtr window, EventRecord *event); |
void DoUpdate(WindowPtr window); |
void DoActivate(WindowPtr window, Boolean becomingActive); |
pascal OSErr QuitApplicationEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon); |
pascal OSErr OpenDocumentsEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon); |
pascal OSErr PrintDocumentsEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon); |
pascal OSErr OpenApplicationEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon); |
void DoEvent(EventRecord *event); |
void AdjustCursor(RgnHandle region); |
void EventLoop(void); |
void Initialize(void); |
void main(void); |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// Globals (The ÒgÓ prefix is used to emphasize that a variable is global.) |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
gMac is used to hold the result of a SysEnvirons call. This makes it |
convenient for any routine to check the environment. It is considered |
global information, anyway. |
*/ |
SysEnvRec gMac; //set up by Initialize |
/* |
gReserveMemory is a handle of reserve memory used by my grow zone |
procedure. If memory is attempted to be allocated and fails, my grow |
zone will release this reserved block of memory. |
*/ |
Handle gReserveMemory; |
/* |
gStatusWindow is the window that tells the user whatÕs happening. I show |
it while a sound is playing, and remove it as soon as the sound is |
complete. It is initialized once during startup, and from then on two |
routines are used to show and hide it. This demonstrates orchestrating |
multimedia, that sound/graphic idea, at least in a crude sort of way. |
*/ |
StatWindowPeek gStatusWindow; |
/* |
gAppResRef is the applicationÕs resource file reference. I need to save |
this since I can open other resource files. The current resource file is |
always gAppResRef unless I momentarily set it to another file to read its |
resources, and then immediately restore it back. |
*/ |
short gAppResRef; //set up by Initialize |
/* |
gInBackground is maintained by our osEvent handling routines. Any part of |
the program can check it to find out if it is currently in the background. |
*/ |
Boolean gInBackground; //maintained by Initialize and DoEvent |
/* |
We have to allocate our own QuickDraw globals when creating code for PowerMacs. |
MetroWerks declares "qd" in their runtime, so don't do that twice. |
*/ |
#if GENERATINGCFM && !defined(__MWERKS__) |
QDGlobals qd; |
#endif |
/* |
This is necessary because of the Apple Event Manager. We used to be able |
to quit directly when the user choose Quit from the File menu. But because |
of the design of Apple Events, we cannot call ExitToShell from the quit handler. |
That event handler must set a flag and then check it in the event loop. |
*/ |
Boolean gTerminate; |
// allocate the RoutineDescriptors for Power Mac toolbox calls |
#if GENERATINGCFM |
CreateRoutineDescriptor(uppGrowZoneProcInfo, MyGrowZone); |
CreateRoutineDescriptor(uppFileFilterYDProcInfo, SFFilter); |
CreateRoutineDescriptor(uppDlgHookYDProcInfo, SFGetHook); |
CreateRoutineDescriptor(uppUserItemProcInfo, DefaultOutline); |
CreateRoutineDescriptor(uppUserItemProcInfo, QuitApplicationEvent); |
CreateRoutineDescriptor(uppUserItemProcInfo, OpenDocumentsEvent); |
CreateRoutineDescriptor(uppUserItemProcInfo, PrintDocumentsEvent); |
CreateRoutineDescriptor(uppUserItemProcInfo, OpenApplicationEvent); |
#endif |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// IMPLEMENTATION |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
InitGraf is always implemented (trap 0xA86E). If the trap table is big |
enough, trap 0xAA6E will always point to either Unimplemented or some other |
trap, but will never be the same as InitGraf. Thus, you can check the size |
of the trap table by asking if the address of trap 0xA86E is the same as 0xAA6E. |
*/ |
#pragma segment Initialize |
short NumToolboxTraps(void) |
{ |
if ( GetToolboxTrapAddress(_InitGraf) == GetToolboxTrapAddress(_InitGraf + 0x0200) ) |
return (0x0200); |
else |
return (0x0400); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Check to see if a given trap is implemented. GetTrapAddress may screw up and |
wrap around into the trap table if you give it a toolbox trap that is out of |
range, so check for this first. |
*/ |
#pragma segment Initialize |
Boolean TrapExists(short theTrap) |
{ |
UniversalProcPtr trapAddress; |
if ( !(theTrap & kOSTrapBit) ) // is this an OS trap? |
trapAddress = GetOSTrapAddress(theTrap); |
else { // no, this is a tool trap |
theTrap &= kTrapNumberMask; // get the trap number |
if ( theTrap < NumToolboxTraps() ) // can this tool trap exist? |
trapAddress = GetToolTrapAddress(theTrap); |
else |
return (false); |
} |
return ( GetToolboxTrapAddress(_Unimplemented) != trapAddress ); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is a very basic grow zone procedure. My application keeps a reserve |
handle of memory in case the Memory Manager gets a request for some memory |
that is not available in my heap. If memory were to get tight (<32k), |
the Toolbox will crash the system, especially Quickdraw. Before releasing |
the reserve handle I make sure it isnÕt the GZSaveHnd. This handle cannot |
be touched by the grow zone procedure. |
WARNING: The grow zone procedure will be called and A5 may not be valid. |
Read Tech Note #136 and 208 |
*/ |
#pragma segment Main |
pascal long MyGrowZone(Size cbNeeded) |
{ |
#pragma unused (cbNeeded) |
long theA5; |
long result; |
theA5= SetCurrentA5(); |
if (((*gReserveMemory) != nil) && (gReserveMemory != GZSaveHnd())) { |
EmptyHandle(gReserveMemory); |
result = kSizeOfReserve; //released this much memory |
} else |
result = 0; //this may release more memory |
theA5= SetA5(theA5); |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Before my application attempts to use more memory, I call this routine |
to check if IÕm already using my reserve memory. If so, then I better |
prepare to die or get my reserve back. |
*/ |
#pragma segment Main |
Boolean LowOnReserve(void) |
{ |
return((*gReserveMemory) == nil); // empty handle is low reserve |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is called from the event loop if LowOnReserve returns that IÕm out of |
the reserve memory. This will recover the reserve memory block. If this |
fails, it will be called the next time through the event loop. |
*/ |
#pragma segment Main |
void RecoverReserve(void) |
{ |
ReallocateHandle(gReserveMemory, kSizeOfReserve); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is called at startup time to allocate the reserve memory block used |
in the grow zone procedure. If IÕm unable to obtain this reserve, then |
return false to signal a failure. |
*/ |
#pragma segment Initialize |
Boolean AllocateReserve(void) |
{ |
gReserveMemory = NewHandle(kSizeOfReserve); |
return(gReserveMemory != nil); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Call PurgeSpace and see if the requested amount of memory exists in the |
heap including a minimal amount of heap space. Also, if my grow zoneÕs |
reserve has been release, thatÕs considered a failure. I donÕt perform |
any purging here. The Memory Manager will do this if it needs the space. |
This routine can be called with a memRequest == 0. This checks if the heap |
space is getting critical. |
*/ |
#pragma segment Main |
Boolean FailLowMemory(long memRequest) |
{ |
long total; |
long contig; |
PurgeSpace(&total, &contig); |
return((total < (memRequest + kMinSpace)) || LowOnReserve()); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This a simple test to see if the user has a currently selected list item. |
*/ |
#pragma segment Main |
Boolean HasSelection(SndDocPeek sndDoc) |
{ |
Cell aCell; |
SetPt(&aCell, 0, 0); |
return(LGetSelect(true, &aCell, sndDoc->list)); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given a document, this will find the currently selected cell in the list. |
Then using this cell as an index number, I call Get1IndResource. This |
will return the proper handle. Since the List Manager is zero based and |
the Resource Manager isnÕt, I have to add one to the index. |
BUG NOTE: GetIndResource will return a bogus handle if the index is not |
positive. |
*/ |
#pragma segment Main |
OSErr GetSelection(SndDocPeek sndDoc, SndListHandle *sndHandle) |
{ |
Cell aCell; |
OSErr result; |
result = noErr; |
SetPt(&aCell, 0, 0); |
if (LGetSelect(true, &aCell, sndDoc->list)) { |
if (aCell.v > -1) { // GetIndResource doesnÕt like < 0 |
UseResFile(sndDoc->resFile); // only get our resources |
*sndHandle = (SndListHandle)Get1IndResource(soundListRsrc, aCell.v + 1); |
if (*sndHandle == nil) |
result = ResError(); // return any error |
UseResFile(gAppResRef); // restore our resource file |
} |
} |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is used to allow the user to select items from the list by using |
the arrow keys. Given a list and the direction, this will select the |
next cell. It will do nothing if weÕre at the first cell and the user |
wants to do even higher (sped), or if weÕre at the last cell and the |
user wants to go even lower (rent a clue bud). |
*/ |
#pragma segment Main |
void SelectNextCell(ListHandle list, Boolean next) |
{ |
Cell aCell; |
short lastItem; |
lastItem = (**list).dataBounds.bottom - 1; // bounds is 1 greater |
SetPt(&aCell, 0, 0); |
if (LGetSelect(true, &aCell, list)) { |
if ((next && (aCell.v < lastItem)) || ((!next) && (aCell.v > 0))) { |
LSetSelect(false, aCell, list); |
if (next) |
aCell.v = aCell.v + 1; |
else |
aCell.v = aCell.v - 1; |
SetPt(&aCell, aCell.h, aCell.v); |
LSetSelect(true, aCell, list); |
LAutoScroll(list); |
} |
} |
else // if no cells selected... |
LSetSelect(true, aCell, list); // select the first cell |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: This routine will go through the list of snd resources |
looking for the one that has the resource ID matching the parameter passed |
in. First thing to do is deselect the current selection. Then once the |
matching resource is found, select that one and scroll it into view. One |
assumption made is that the resources and the list are both index in the |
same order. This is true for the sndDoc, since I built the list this way. |
*/ |
#pragma segment Main |
void SelectSndCell(SndDocPeek sndDoc, short resID) |
{ |
Str255 name; |
Cell aCell; |
Handle sndHndle; |
ResType rType; |
short testID; |
short index; |
short numSnd; |
index = 0; |
aCell.h = 0; |
aCell.v = 0; |
if (LGetSelect(true, &aCell, sndDoc->list)) |
LSetSelect(false, aCell, sndDoc->list); //deselect any cell |
UseResFile(sndDoc->resFile); //count only its resources |
numSnd = Count1Resources(soundListRsrc); //number of sounds available |
do { |
index++; |
SetResLoad(false); //donÕt load any resources |
sndHndle = Get1IndResource(soundListRsrc, index); //only get snd from file |
SetResLoad(true); //back to normal resource operations |
GetResInfo(sndHndle, &testID, &rType, name); |
} while ((resID != testID) && (index < numSnd)); |
UseResFile(gAppResRef); //restore our resource file |
aCell.h = 0; |
aCell.v = index - 1; |
LSetSelect(true, aCell, sndDoc->list); |
LAutoScroll(sndDoc->list); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is used to create the list showing all the snd resources in the file. |
If anything goes wrong while attempting to get any of the resources, then I |
return false. I donÕt want the document to continue if something goes |
wrong while reading a resource. If I do get the resource, then I add its |
name to the list in a new row. IÕm assured that at least one resource is |
in the file before this routine is called. Untitled resources will get one. |
*/ |
#pragma segment Open |
OSErr InitSndList(SndDocPeek docPtr) |
{ |
short index; |
short resID; |
short newRow; |
short numSnd; |
Handle resHandle; |
StringHandle strHandle; |
ResType itsType; |
Str255 resName; |
Str255 untitled; |
Cell aCell; |
OSErr result = noErr; |
strHandle = (StringHandle)Get1Resource('STR ', rUntitled); |
if (strHandle != nil) |
PStringCopy(*strHandle, untitled); // save no name title |
else |
untitled[0] = 0; // at least an empty string |
UseResFile(docPtr->resFile); |
numSnd = Count1Resources(soundListRsrc); |
newRow = LAddRow(numSnd, 0, docPtr->list); |
for (index = 1; index <= numSnd; index++) { |
SetResLoad(false); // donÕt load any resources |
resHandle = Get1IndResource(soundListRsrc, index); // only get snd from file |
SetResLoad(true); // back to normal operations |
if (resHandle != nil) { // only if I got the snd |
GetResInfo(resHandle, &resID, &itsType, resName); |
if (StrLength(resName) == 0) // if the snd isnÕt named... |
PStringCopy(untitled, resName); // give it a name |
SetPt(&aCell, 0, index - 1); |
LSetCell(&resName[1], StrLength(resName), aCell, docPtr->list); |
} |
else { // resHandle == NIL |
result = resNotFound; // problem with resource file |
break; // get out of the loop |
} |
} |
UseResFile(gAppResRef); // restore our resource file |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
In the case that the user has attempted to open a resource file that is |
already open, this routine will scan my open documents for it. If I find |
that the requested file is already open by the application, then return |
true and its window pointer. The application can then simply select that |
window. If the resource file is open but not by this application, thatÕs |
a problem and I donÕt use the resource file. Many problems with using an |
open resource file. Now if the Resource Manager would take care of this |
problem for us... |
*/ |
#pragma segment Open |
Boolean OpenByApp(FSSpecPtr file, WindowPtr *window) |
{ |
Str255 docName; |
WindowPtr testWindow; |
Boolean result; |
result = false; |
testWindow = FrontWindow(); |
while (testWindow != nil) { |
if (GetWRefCon(testWindow) == rSoundWindow) |
if ((file->vRefNum == ((SndDocPeek)testWindow)->vRefNum) |
&& (file->parID == ((SndDocPeek)testWindow)->dirID)) |
{ |
GetWTitle(testWindow, docName); |
if (EqualString(file->name, docName, false, true)) |
{ |
*window = testWindow; |
result = true; |
break; |
} |
} |
testWindow = (WindowPtr)(((WindowPeek)testWindow)->nextWindow); |
} |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Get the global coordinates of the mouse. Get the global coordinates by |
calling GetMouse and LocalToGlobal. This assumes the current port is a |
valid graf port. When wouldnÕt it be? |
*/ |
#pragma segment Main |
Point GetGlobalMouse(void) |
{ |
Point globalPt; |
GetMouse(&globalPt); |
LocalToGlobal(&globalPt); |
return(globalPt); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Try and determine the windowÕs title bar height. This isnÕt easy, |
especially if the window is invisible. We all know how the standard Apple |
System WDEF works, at least at this date we do. I assume that method as |
shown below. I could do what MacApp does with the Function CallWDefProc. |
If the user has installed a replacement for the System WDEF, then itÕs |
their problem to deal with. My method will work as long as Apple doesnÕt |
change how the WDEF in System 6.0 calculates the title bar height. This |
will allow my application to work on international Macs that have a larger |
system font than Chicago. I check the windowÕs variant code for one that |
includes a title bar. I will have to change this routine to adjust for |
the modal-moveable window type, which hasnÕt been defined yet. |
In this routine, I violate my rule about not using the GetPort, SetPort, |
SetPort sequence mentioned at the start of the file. Mostly, I do this |
because itÕs not all that apparent that a routine called GetWTitleHeight |
will change the port, so I make sure that it doesnÕt. |
*/ |
#pragma segment Open |
short GetWTitleHeight(short variant) |
{ |
FontInfo info; |
GrafPtr curGraf; |
GrafPtr wMgrPort; |
short wTitleHeight; |
short result; |
if ( (variant == documentProc) |
|| (variant == noGrowDocProc) |
|| (variant == zoomDocProc) |
|| (variant == rDocProc) ) |
{ |
GetPort(&curGraf); |
GetWMgrPort(&wMgrPort); //I need to know the font... |
SetPort(wMgrPort); //info in the SystemÕs port |
GetFontInfo(&info); |
SetPort(curGraf); //restore current port |
wTitleHeight = info.ascent + info.descent + info.leading + 2; |
if (wTitleHeight < kMinWindowTitleHeight) |
wTitleHeight = kMinWindowTitleHeight; |
result = wTitleHeight; |
} else |
result = 0; //other window types have no title |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given a window, this will return the top left point of the windowÕs |
port in global coordinates. Something this doesnÕt include, is the |
windowÕs drag region (or title bar). This returns the top left point |
of the windowÕs content area only. |
In this routine, I violate my rule about not using the GetPort, SetPort, |
SetPort sequence mentioned at the start of the file. Mostly, I do this |
because itÕs not all that apparent that a routine called GetGlobalTopLeft |
will change the port, so I make sure that it doesnÕt. |
*/ |
#pragma segment Main |
Point GetGlobalTopLeft(WindowPtr window) |
{ |
GrafPtr theGraf; |
Point globalPt; |
GetPort(&theGraf); |
SetPort(window); |
globalPt = TopLeft(window->portRect); |
LocalToGlobal(&globalPt); |
SetPort(theGraf); |
return(globalPt); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
IÕm using string pointers (not necessarily Memory Manager pointers) |
to the replacement strings. Using Str255 would copy the string to the |
stack, and then to the text handle. This way it is only copied once. |
*/ |
#pragma segment Main |
void MyParamText(StringHandle text, StringPtr cite0, StringPtr cite1) |
{ |
long offSet; |
char param0[2] = {'^', '0'}; |
char param1[2] = {'^', '1'}; |
long newLength; |
if (StrLength(cite0) > 0) |
offSet = Munger((Handle)text, 1, param0, sizeof(param0), (Ptr)&(cite0[1]), StrLength(cite0)); |
if (StrLength(cite1) > 0) |
offSet = Munger((Handle)text, 1, param1, sizeof(param1), (Ptr)&(cite1[1]), StrLength(cite1)); |
newLength = GetHandleSize((Handle)text) - 1; |
if (newLength > 255) |
newLength = 255; |
(*text)[0] = newLength; // stringÕs new length byte |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given a windowÕs portRect, this routine will center the rectangle on the |
main device within the desktop region. This excludes the menu bar area. |
It follows the Apple Human Interface Guidelines for where to place a |
window centered on the screen. That is, 1/3th of the desktop shows above |
the window and 2/3ths below it. It returns a new rectangle set to the |
centered position. The top and left of this rectangle can be used with |
MoveWindow. This routine only considers the main device. CenterWindowRect |
could take a GDevice as a parameter to center the VAR rect with. This |
would also allow Alerts or dialogs that are closely associated with a |
document window to be centered relative to the monitor that contains that |
document. This is also one of the Human Interface Guidelines. MacApp 2.0 |
has a utility CalcScreenRect that you can borrow from to include this. |
WARNING: This routine may move or purge memory. |
*/ |
#pragma segment Open |
void CenterWindowRect(short variant, Rect *theRect) |
{ |
Point rectSize; |
short wTitleHeight; |
wTitleHeight = GetWTitleHeight(variant); //get title height |
SetPt(&rectSize, theRect->right, theRect->bottom + wTitleHeight); //get size of rect |
SubPt(TopLeft(*theRect), &rectSize); // include it in size |
theRect->top = ((qd.screenBits.bounds.bottom - qd.screenBits.bounds.top - GetMBarHeight() - rectSize.v) |
/ 3) + GetMBarHeight(); //1/3th below menubar, centered horz |
theRect->left = ((qd.screenBits.bounds.right - qd.screenBits.bounds.left) - rectSize.h) / 2; |
SetPt(&BottomRight(*theRect), theRect->left, theRect->top); //return adjusted rect |
AddPt(rectSize, &BottomRight(*theRect)); |
theRect->top += wTitleHeight; //remove title height from rect |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given a window ID, this routine will center the windowÕs rectangle before |
showing it on the main screen. This follows the Apple Human Interface |
Guidelines for where to place a centered window on the screen. If the |
window is closely associated with another window, considerations should be |
given to where that other window is located. If this other window is on a |
screen other then the main monitor, then it would be best to center the |
window on that other monitor. |
VERSION 1.1: There is a problem with GetNewWindow in the old Mac Plus and |
SE ROMS. It will call ReleaseResource on the 'WIND' resource within |
GetNewWindow. This make the handle invalid after the call. Therefore, |
before calling HPurge, we get the resource once again. |
*/ |
#pragma segment Open |
WindowPtr GetCenteredWindow(short id, Ptr p, WindowPtr behind) |
{ |
Rect newRect; |
WindowTHndl windTemplate; |
WindowPtr window; |
window = nil; //initialize result |
windTemplate = (WindowTHndl)(Get1Resource('WIND', id)); |
if (windTemplate != nil) { |
HNoPurge((Handle)windTemplate); |
newRect = (**windTemplate).boundsRect; |
CenterWindowRect((**windTemplate).procID, &newRect); |
(**windTemplate).boundsRect = newRect; |
window = GetNewWindow(id, p, behind); |
HPurge(Get1Resource('WIND', id)); |
} |
return(window); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given an Alert ID, this routine will center the alertÕs rectangle before |
showing it on the main screen. This follows the Apple Human Interface |
Guidelines for where to place a centered window on the screen. If the |
Alert is closely associated with another window, considerations should be |
given to what the window is located. If this other window is on a screen |
other then the main monitor, then it would be best to center the Alert on |
that other monitor. |
VERSION 1.2: System 7 supports auto-positioning of windows and dialog, so |
this routine isn't being called anymore. I has been left in the sources |
for the readers that may be interested in knowing how it works. |
*/ |
#pragma segment Open |
short CenteredAlert(short alertID) |
{ |
AlertTHndl alertHandle; |
Rect alertRect; |
alertHandle = (AlertTHndl)(Get1Resource('ALRT', alertID)); |
if (alertHandle != nil) { |
HNoPurge((Handle)alertHandle); |
alertRect = (**alertHandle).boundsRect; |
CenterWindowRect(dBoxProc, &alertRect); |
(**alertHandle).boundsRect = alertRect; |
HPurge((Handle)alertHandle); |
} |
return(Alert(alertID, nil)); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given a dialog ID, this routine will center the dialogÕs rectangle before |
showing it on the main screen. This follows the Apple Human Interface |
Guidelines for where to place a centered window on the screen. If the |
dialog is closely associated with another window, considerations should be |
given to what the window is located. If this other window is on a screen |
other then the main monitor, then it would be best to center the dialog on |
that other monitor. |
VERSION 1.1: There is a problem with GetNewDialog in the old Mac Plus and |
SE ROMS. It will call ReleaseResource on the 'WIND' resource within |
GetNewWindow. This make the handle invalid after the call. Therefore, |
before calling HPurge, we get the resource once again. |
VERSION 1.2: System 7 supports auto-positioning of windows and dialog, so |
this routine isn't being called anymore. I has been left in the sources |
for the readers that may be interested in knowing how it works. |
*/ |
#pragma segment Open |
DialogPtr GetCenteredDialog(short id, Ptr p, WindowPtr behind) |
{ |
Rect newRect; |
DialogTHndl dlogTemplate; |
DialogPtr dialog; |
dialog = nil; //initialize result |
dlogTemplate = (DialogTHndl)Get1Resource('DLOG', id); |
if (dlogTemplate != nil) { |
newRect = (**dlogTemplate).boundsRect; |
CenterWindowRect((**dlogTemplate).procID, &newRect); |
(**dlogTemplate).boundsRect = newRect; |
HNoPurge((Handle)dlogTemplate); |
dialog = GetNewDialog(id, p, behind); |
HPurge(Get1Resource('DLOG', id)); |
} |
return(dialog); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given any control handle, this will draw an outline around it. This is |
used for the default button of a window. The extra nice feature here is |
that IÕll erase the outline for buttons that are inactive. Seems like |
there should be a Toolbox call for getting a controlÕs hilite state. |
Since there isnÕt, I have to look into the control record myself. This |
should be called for update and activate events. |
The method for determining the oval diameters for the roundrect is a |
little different than that recommended by Inside Mac. IM I-407 suggests |
that you use a hardcoded (16,16) for the diameters. However, this only |
looks good for small roundrects. For larger ones, the outline doesnÕt |
follow the inner roundrect because the CDEF for simply buttons doesnÕt |
use (16,16). Instead, it uses half the height of the button as the |
diameter. By using this formula, too, our outlines look better. |
WARNING: This will set the current port to the controlÕs window. |
*/ |
#pragma segment Main |
void DoButtonOutline(ControlHandle button) |
{ |
Rect theRect; |
PenState curPen; |
short buttonOval; |
if (button != nil) { |
SetPort((**button).contrlOwner); |
GetPenState(&curPen); |
PenNormal(); |
theRect = (**button).contrlRect; |
InsetRect(&theRect, kButtonFrameInset, kButtonFrameInset); |
buttonOval = (theRect.bottom - theRect.top) / 2; |
if (((**button).contrlHilite == kControlNoPart)) |
PenPat(&qd.black); |
else |
PenPat(&qd.gray); |
PenSize(kButtonFrameSize, kButtonFrameSize); |
FrameRoundRect(&theRect, buttonOval, buttonOval); |
SetPenState(&curPen); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given the button control handle, this will cause the button to look as |
if it has been clicked in. This is nice to do for the user if they type |
return or enter to select the default item. |
*/ |
#pragma segment Main |
void SelectButton(ControlHandle button) |
{ |
unsigned long finalTicks; |
HiliteControl(button, kHiliteControlSelect); |
Delay(kDelayTime, &finalTicks); |
HiliteControl(button, kHiliteControlDeselect); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given a sound document, this routine goes through the control list looking |
for a control that needs to be activated, or deactivated what ever the |
case maybe. The Play button is the default button, and it also needs its |
outline drawn. The Stop button is always active on the front most window. |
This is true even if there is no selection made in the windowÕs list. The |
Stop button is a global action, regardless of what is happening in the window. |
VERSION 1.1: Stop button is only active while a sound is playing. |
*/ |
#pragma segment Main |
void ActivateSndCntls(SndDocPeek sndDoc) |
{ |
ControlHandle control; |
long cntlRefCon; |
Boolean activate; |
activate = ((WindowPeek)sndDoc)->hilited && HasSelection(sndDoc); |
control = (ControlHandle)((WindowPeek)sndDoc)->controlList; |
while (control != nil) { |
cntlRefCon = GetControlReference(control); |
if ( (cntlRefCon == rPlaySndCntl) |
|| (cntlRefCon == rHyperPlayCntl) |
|| (cntlRefCon == rPlayScaleCntl) |
|| (cntlRefCon == rMelodyCntl)) { |
if (activate) |
HiliteControl(control, kControlNoPart); |
else |
HiliteControl(control, kControlInactivePart); |
if (cntlRefCon == rPlaySndCntl) |
DoButtonOutline(control); |
} |
if (cntlRefCon == rStopCntl) { |
if (((WindowPeek)sndDoc)->hilited && HasChannelOpen()) |
HiliteControl(control, kControlNoPart); |
else |
HiliteControl(control, kControlInactivePart); |
} //cntlRefCon == rStopCntl |
if (cntlRefCon == rRecordCntl) { |
if (((WindowPeek)sndDoc)->hilited) |
HiliteControl(control, kControlNoPart); |
else |
HiliteControl(control, kControlInactivePart); |
} //cntlRefCon == rRecordCntl |
control = (**control).nextControl; |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Check if a window belongs to a desk accessory. This will first test for a |
nil window. DAs will have a negitive windowKind. |
*/ |
#pragma segment Main |
Boolean IsDAWindow(WindowPtr window) |
{ |
if (window == nil) |
return(false); |
else //DA windows have negative windowKinds |
return(((WindowPeek)window)->windowKind < 0); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Check to see if a window is a document window. This will first test for a |
nil window. Document windows all have a refCon set to rSoundWindow. This |
insures there is document type information after the window record. |
*/ |
#pragma segment Main |
Boolean IsDocWindow(WindowPtr window) |
{ |
Boolean result; |
if (window == nil) |
result = false; |
else |
result = (GetWRefCon(window) == rSoundWindow); |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This test will return true for a window that needs to be treated as a |
modal window. To include additional windows, add the windowÕs refCon |
value to the set of modal windows. |
*/ |
#pragma segment Main |
Boolean IsModalWindow(WindowPtr window) |
{ |
long wRef; |
Boolean result; |
if (window == nil) |
result = false; |
else { |
wRef = GetWRefCon(window); |
result = (wRef == rAboutWindow); // test for all our modal windows |
} |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is used to free up all the sound data and channels in use. There are |
two reasons for using this routine. One is after a sound has completed |
the other is to terminate a sound that may be in progress. For this |
second reason, this routine should always be used before playing a new |
sound. When disposing of a sound channel, this sets all documentÕs |
sndInUse flags to false. I can call this routine at any time. When I |
find that the memory reserve is used, I will call this to free up any |
possible sound resources which tend to be large. Another important point |
is the human interface issue of hiding the status window. ItÕs possible |
that playing some short sounds could cause the status window to disappear |
before the user had much of a chance to see it. To solve this, there is a |
delay that insures the status window was visible for a minimal time. |
VERSION 1.1: Update the document's controls since know that the sound |
is no longer playing the buttons in the document window need to be updated. |
*/ |
#pragma segment Main |
void KillSound(void) |
{ |
WindowPtr window; |
if (HasChannelOpen()) { // if a channel is open... |
window = FrontWindow(); // set all documentÕs flags |
while (window != nil) { |
if (IsDocWindow(window)) // if this is my document... |
((SndDocPeek)window)->sndInUse = false; // then it is no longer active |
window = (WindowPtr)((WindowPeek)window)->nextWindow; |
} |
} |
if (HasSoundCompleted()) { // why were we called? |
DoSoundComplete(); // from completion |
// delay for status window to show a minimal amount of time |
while ((TickCount() - gStatusWindow->showTime) < kShowTimeDelay) {}; |
} |
else |
FreeAllChans(); // or just because |
HideWindow((WindowPtr)gStatusWindow); |
window = FrontWindow(); |
if (IsDocWindow(window)) |
ActivateSndCntls((SndDocPeek)window); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Close any of my windows. At this point, if there was a document |
associated with a window, you could do any document saving processing if |
it has been changed since being opened. DoCloseWindow would return true |
if the window actually closed. false would be returned, as an example, if |
the user chose ÒCancelÓ in the Save AsÉ dialog. An important detail is if |
the window to be close is one of my documents that is currently playing a |
sound. We cannot dispose of it yet, since the sound resource owned by the |
document would be in use by the Sound Manager. So, I test the documentÕs |
sndInUse flag and if it is in use I call KillSound. The About window |
contains some resource handles. I only mark them purgeable, but I could |
use ReleaseResource and then DisposHandle. |
*/ |
#pragma segment Main |
Boolean DoCloseWindow(WindowPtr window) |
{ |
Boolean result = true; |
switch (GetWRefCon(window)) { |
case rSoundWindow: |
if (((SndDocPeek)window)->sndInUse) // contain a snd in use? |
KillSound(); // not any more |
if (((SndDocPeek)window)->list != nil) // dispose of document data |
LDispose(((SndDocPeek)window)->list); |
if (((SndDocPeek)window)->resFile != 0) |
CloseResFile(((SndDocPeek)window)->resFile); |
CloseWindow((WindowPtr)window); |
DisposePtr((Ptr)window); // dispose of our doc storage |
break; |
case rStatusWindow: |
DisposeHandle((Handle)gStatusWindow->message); |
CloseWindow((WindowPtr)gStatusWindow); |
DisposePtr((Ptr)gStatusWindow); |
break; |
case rAboutWindow: |
if (((AboutWPeek)window)->comment != nil) // never dispose... |
HPurge(((AboutWPeek)window)->comment); // a Resource handle |
if (((AboutWPeek)window)->appPict != nil) |
HPurge(((AboutWPeek)window)->appPict); |
CloseWindow((WindowPtr)window); |
DisposePtr((Ptr)window); |
break; |
default: |
if (IsDAWindow(window)) // we can close DAs too |
CloseDeskAcc(((WindowPeek)window)->windowKind); |
break; |
} // switch GetWRefCon(window) |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Display an alert to inform the user of an error. MessageID acts as an |
index into a STR# resource of error messages. This will attempt to |
replace the error number with a string if the sound is a Sound Manager |
error. |
BUG NOTE: GetIndString will return a bogus string if the index is not |
positive. |
*/ |
#pragma segment Main |
void AlertUser(short error, short messageID) |
{ |
Str255 msg1; |
Str255 msg2; |
short theItem; |
UseResFile(gAppResRef); // restore our resource file |
if (messageID > 0) |
GetIndString(msg1, sErrStrings, messageID); |
else |
msg1[0] = 0; // case thereÕs no message |
if ((error <= noHardware) && (error >= badFormat)) |
GetIndString(msg2, sSMErrStrings, AbsoluteValue(error) + noHardware + 1); |
else |
NumToString(error, msg2); |
ParamText(msg1, msg2, "\p", "\p"); |
theItem = Alert(rUserAlert, nil); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Display an alert that tells the user an error occurred, then exit the |
program. This routine is used as an ultimate bail-out for serious errors |
that prohibit the continuation of the application. Errors that do not |
require the termination of the application are handled with AlertUser. |
Error checking and reporting has a place even in the simplest application. |
BUG NOTE: GetIndString will return a bogus string if the index is not |
positive. |
*/ |
#pragma segment Main |
void EmergencyExit(short message) |
{ |
Str255 msg; |
short theItem; |
SetCursor(&qd.arrow); |
if (message > 0) |
GetIndString(msg, sErrStrings, message); |
else |
msg[0] = 0; // case thereÕs no message |
ParamText(msg, "\p", "\p", "\p"); |
theItem = Alert(rExitAlert, nil); |
ExitToShell(); //gotta go! |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Clean up the application and exit. I close all of the windows so that |
they can update their documents, if any. Dispose of the SoundUnit and |
close the status window if the user really wants to quit. |
*/ |
#pragma segment Main |
void Terminate(void) |
{ |
WindowPtr aWindow; |
Boolean closed; |
closed = true; |
KillSound(); //stop any sound in progress |
do { |
aWindow = FrontWindow(); //get the current front window |
if (aWindow != nil) |
closed = DoCloseWindow(aWindow); //close this window |
} while ((closed) && (aWindow != nil)); //do all windows |
if (closed) { |
FreeSoundUnit(); //get rid of all sound equipment |
gTerminate = true; //exit if no cancellation |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Draw the status message and update the button. I setup a rectangle that |
is the text area. This is the entire window area, inset a small amount. |
I also subtract from the bottom of the rectangle the area used by the button. |
Doing things this way allowed me to change the size of the window and |
not change any of this code. The rectangle used by text will fill the |
window regardless of the new size I give it in the WIND resource. |
*/ |
#pragma segment Main |
void DrawStatusWindow(void) |
{ |
Rect theRect; |
PenNormal(); |
theRect = ((WindowPtr)gStatusWindow)->portRect; |
theRect.bottom = theRect.bottom - kDafaultButSizeH; |
InsetRect(&theRect, kStadardWhiteSpacing, kStadardWhiteSpacing); |
HLock((Handle)gStatusWindow->message); |
TETextBox((*(gStatusWindow->message) + 1), |
**(gStatusWindow->message), &theRect, teJustLeft); |
HUnlock(((Handle)gStatusWindow->message)); |
UpdateControls((WindowPtr)gStatusWindow, ((WindowPtr)gStatusWindow)->visRgn); |
DoButtonOutline((ControlHandle)((WindowPeek)gStatusWindow)->controlList); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Whenever I start a sound, I show this status window. It will contain a |
message showing what is currently happening. A very good rule is not to |
perform drawing outside of the update event. I bend that rule here. I |
could simply select this window and show it. This would allow my normal |
drawing and activating routines to be called. The problem with that was, |
I found that many times the status window appeared and then disappeared |
before these update routines had a chance to draw the window. This left |
the user with a blank window that immediately went away. So, I do the |
activation and drawing right after showing it. |
BUG NOTE: GetIndString will return a bogus string if the index is not |
positive. |
*/ |
#pragma segment Main |
void ShowStatusWindow(short messageID) |
{ |
Str255 msg; |
if (FailLowMemory(0)) { // running low on memory? |
KillSound(); |
AlertUser(memFullErr, sLowMemory); |
} |
else { |
if (messageID > 0) |
GetIndString(msg, sMsgStrings, messageID); |
else |
msg[0] = 0; // case thereÕs no message |
SetString(gStatusWindow->message, msg); |
ShowWindow((WindowPtr)gStatusWindow); // show the window |
SelectWindow((WindowPtr)gStatusWindow); // bring it to the front |
SetPort((WindowPtr)gStatusWindow); |
EraseRect(&(((WindowPtr)gStatusWindow)->portRect)); // erase old message |
HiliteControl((ControlHandle)((WindowPeek)gStatusWindow)->controlList, kControlNoPart); |
DrawStatusWindow(); // draw the new message |
ValidRect(&(((WindowPtr)gStatusWindow)->portRect)); // avoid needless update |
gStatusWindow->showTime = TickCount(); // itÕs show time folks |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This initializes the status window, and keeps it hidden until the user |
decides to play a sound. I also center it for the first time, but the |
user is free to drag it to a new location afterwards. The status window |
also has a message associated to it, so I allocate a string handle for this. |
*/ |
#pragma segment Initialize |
void InitStatusWindow(void) |
{ |
WindowPtr window; |
ControlHandle stopButton; |
gStatusWindow = (StatWindowPeek)NewPtrClear(sizeof(StatusWindow)); |
if (gStatusWindow == nil) |
EmergencyExit(sInitStatusErr); |
window = GetNewWindow(rStatusWindow, (Ptr)gStatusWindow, (WindowPtr)-1); |
if (window == nil) |
EmergencyExit(sInitStatusErr); |
SetWRefCon(window, rStatusWindow); |
stopButton = GetNewControl(rCancelCntl, window); |
if (stopButton == nil) |
EmergencyExit(sInitStatusErr); |
gStatusWindow->message = NewString("\p"); // a new empty string handle |
if (gStatusWindow->message == nil) |
EmergencyExit(sInitStatusErr); |
HNoPurge((Handle)gStatusWindow->message); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
I just flash the menubar for a moment instead of playing any sounds. ItÕs |
the same effect as setting the sound volume to zero but that would prevent |
me playing my sounds. Also, it helps to prevent the problem of _SysBeep |
being called by the Dialog Manager while IÕm playing a sound. If my |
application is playing a sound and, for example, the user clicks outside |
of the Standard File dialog ModalDialog calls _SysBeep. This can cause |
the Mac to crash or trash my channel. |
BUG NOTE: If the current Sound Manager were playing a sound and a |
_SysBeep were to occur, bad things could happen on a Mac Plus/SE. Either |
the applicationÕs channel would be trashed or the Mac could crash. |
VERSION 1.1: The new Sound Manager will handle the problem presented in the |
older Sound Manager regarding SysBeep. If I'm running under the new Sound |
Manager then I'll call SysBeep anyway. The new Sound Manager will properly |
handle the situation of an open sound channel when SysBeep is called. |
VERSION 1.2: This is no longer needed since we only use Sound Manager 2 or later. |
*/ |
#pragma segment Main |
pascal void DoErrorSound(short soundNo) |
{ |
unsigned long finalTicks; |
if (GetSoundMgrVersion() == 1) { |
if (soundNo > 0) { //only after the first time |
FlashMenuBar(0); |
Delay(kDelayTime, &finalTicks); |
FlashMenuBar(0); |
} |
} |
else |
SysBeep(30); //does the right thing now |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This tests if the volume has been set very low. ItÕs a judgement call as |
to what Òtoo lowÓ means. The user sets the volume, so how could it be too |
low? A setting of 0 could be considered too low, but in any case do not |
adjust it. I find the volume goes below 4 on a standard Mac, then there |
is a perceivable difference. I also wanted to show this routine as a |
demonstration in how to handle such a situation. Do not change it |
directly! Ask the user to do it. Consider the user that has their Mac |
connected to a stack of Marshalls (which go to 11) and has purposely set |
their MacÕs volume where they wanted it. This may be 3 and if you crack |
it up to 8, it could be a scene out of Back to the Future. |
User Interface rule, let the user remain in control! |
VERSION 1.2: Don't use GetSoundVol anymore. This just reads a low memory global |
which is no longer valid with Sound Manager 3 or later. The old low global |
is only 3 bits, which is why the range used to be 0-7. There are new machines |
supported by Sound Manager 3 that have more than 8 volume level. So the |
old global is scaled into the 0-7 range. You should avoid all of this non-sense |
and start using the new calls of Sound Manager 3, and start using percentages. |
*/ |
#pragma segment Initialize |
void CheckSoundVolume(void) |
{ |
Fixed percent; |
long soundVolume; |
short curVolume; |
short item; |
Str255 numStr; |
if (GetSoundMgrVersion() < 3) |
{ |
// turn old 0-7 range into the new 0-0x0100 range |
curVolume = LMGetSdVolume(); // same as calling GetSoundVol() |
curVolume *= kFullVolume; |
curVolume += 7-1; // avoid rounding errors |
curVolume /= 7; // divid by old max |
} |
else |
{ |
// get the current stereo levels and average them |
GetDefaultOutputVolume(&soundVolume); |
curVolume = (soundVolume >> 16) + (soundVolume & 0x0000FFFF); |
curVolume /= 2; |
} |
// take the current level and turn this in to a percentage of full volume, |
// then show the user that the level is low and how they may increase it. |
percent = FixDiv( (long)curVolume << 16, kFullVolume << 16); |
percent = FixMul(percent, 100L << 16); |
NumToString(percent >> 16, numStr); |
ParamText(numStr, "\p", "\p", "\p"); // show the current volume |
item = Alert(rSoundVolAlert, nil); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Simply add all the buttons I want to use in the document window. I get |
the control templates, and add the control to the window. I donÕt check |
for getting errors for two reasons. I put these controls there. If the |
user removes them, itÕs their problem. If the application cannot get the |
memory required for these, then the application is already out of memory |
and checking here probably wouldnÕt help much. I do check before creating |
the document that there was enough available memory. I save the resource |
ID into the controlÕs refCon so later I can tell which control was hit by |
the user. |
VERSION 1.1: Add the record button to the window.} |
*/ |
#pragma segment Open |
void AddSndDocControls(WindowPtr window) |
{ |
ControlHandle control; |
control = GetNewControl(rPlaySndCntl, window); |
if (control != nil) |
SetControlReference(control, rPlaySndCntl); |
control = GetNewControl(rHyperPlayCntl, window); |
if (control != nil) |
SetControlReference(control, rHyperPlayCntl); |
control = GetNewControl(rPlayScaleCntl, window); |
if (control != nil) |
SetControlReference(control, rPlayScaleCntl); |
control = GetNewControl(rMelodyCntl, window); |
if (control != nil) |
SetControlReference(control, rMelodyCntl); |
control = GetNewControl(rStopCntl, window); |
if (control != nil) |
SetControlReference(control, rStopCntl); |
control = GetNewControl(rRecordCntl, window); |
if (control != nil) |
SetControlReference(control, rRecordCntl); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
#pragma segment Open |
Boolean PositionAvailable(Point newPt, short wTitleHeight) |
{ |
WindowPtr oldWindow; |
Point delta; |
Point oldPt; |
Boolean taken; |
taken = false; |
oldWindow = FrontWindow(); |
while ((oldWindow != nil) && !taken) { |
if (IsDocWindow(oldWindow) && ((WindowPeek)oldWindow)->visible) { |
oldPt = GetGlobalTopLeft(oldWindow); |
delta.v = AbsoluteValue(newPt.v - oldPt.v); |
delta.h = AbsoluteValue(newPt.h - oldPt.h); |
taken = (delta.h + delta.v) |
<= ((kWindowPosStaggerH + kWindowPosStaggerV + wTitleHeight) / 2); |
} |
oldWindow = (WindowPtr)(((WindowPeek)oldWindow)->nextWindow); |
} |
return(!taken); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
There are a few approaches I thought of and tried, none of which satisfied |
me. ResEdit uses the simplest method, an offset from the front window. I |
donÕt like this because it may cover up existing windows. So my approach is |
to go through the window list looking for an empty spot. If the empty |
spot causes the window to go off the screen, then I start a new stack off |
to the right. This will cause a new series of window to be stacked. If the |
new stack would cause the window to go off screen, then itÕs time to give up. |
All new windows will then be moved to the starting point and there is no |
further stacking. If I look at this routine too much, I get real tired. |
If I try to change it at all, I get woozy trying to make it work again. |
VERSION 1.2: System 7 provides for staggering windows, but it doesn't work |
exactly the way I want it. Mine's better. |
*/ |
#pragma segment Open |
WindowPtr NewStackedWindow(short windID, Ptr windStorage) |
{ |
Point windSize; |
Point newPt; |
WindowPtr newWindow; |
short wTitleHeight; |
short numStacks; |
Boolean taken; |
//set the initail starting point for a window, less the staggering amount |
newWindow = GetNewWindow(windID, windStorage, (WindowPtr)-1); |
windSize = BottomRight(newWindow->portRect); |
SubPt(TopLeft(newWindow->portRect), &windSize); |
wTitleHeight = GetWTitleHeight(GetWVariant(newWindow)); |
SetPt(&newPt, kWindowPosStartPt - kWindowPosStaggerH, kWindowPosStartPt - kWindowPosStaggerV + GetMBarHeight()); |
numStacks = 0; |
do { //add the staggering amount then test if the window goes off the screen |
taken = true; |
SetPt(&newPt, newPt.h + kWindowPosStaggerH, newPt.v + kWindowPosStaggerV + wTitleHeight); |
if ( ((newPt.v + windSize.v) > qd.screenBits.bounds.bottom) |
|| ((newPt.h + windSize.h) > qd.screenBits.bounds.right)) |
{ |
numStacks++; |
SetPt(&newPt, kWindowPosStartPt + (numStacks * kWindowPosStaggerH), |
kWindowPosStartPt + wTitleHeight + GetMBarHeight()); |
if (((newPt.v + windSize.v) > qd.screenBits.bounds.bottom) |
|| ((newPt.h + windSize.h) > qd.screenBits.bounds.right)) { |
SetPt(&newPt, kWindowPosStartPt, kWindowPosStartPt + wTitleHeight + GetMBarHeight()); |
taken = false; |
} |
} |
} while (taken && !(PositionAvailable(newPt, wTitleHeight))); |
MoveWindow(newWindow, newPt.h, newPt.v, true); |
return(newWindow); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Create a new sound document and initialize all the data associated with |
it. If I encounter an error, then dispose of any memory allocated in the |
attempt. Allocate all necessary memory required for a sound document. I |
like to use NewPtrClear because is will initialize all the memory. I |
depend on real values or zero in my document fields. Then its time to |
build a sound document which contains all the controls and the list of |
sounds. I get the position for the list kept in a rectangle resource. |
This helps me to adjust the size of the list without changing code. I |
will make an additional adjustment to the listÕs height to insure better |
scrolling. This avoids the ugly white space of an improperly sized |
rectangle. If I cannot create the list, IÕll close the window and return |
a NIL. If I find after creating a new document that IÕm low on memory, |
IÕll close it and return an error. |
SPECIAL NOTE: The Human Interface Group suggested that the buttons and the |
list should in the System font. I designed these windows using the |
application font in 10 point. If you change the call to TextFont, you |
find that the buttons and the list will work properly in your font choice. |
VERSION 1.1: Size the window smaller to hide the record button from the |
user if Sound Input is not available. Added memory check to the beginning |
of this routine before creating the document. This was done because there |
are two routines that call this one, OpenSoundDoc and NewSoundDoc which |
both performed this check. Having it here in one place reduces the chance of |
future bugs (doing the same thing in two places) and reduces the size of |
the code. |
*/ |
#pragma segment Open |
OSErr CreateSoundDoc(short resRef, FSSpecPtr file) |
{ |
SndDocPeek newDocPtr; |
WindowPtr window; |
ListHandle list; |
RectHandle rHandle; |
Rect lView; |
Rect lBounds; |
Cell aCell; |
Point cSize; |
Point newSize; |
short maxListHeight; |
OSErr theErr; |
Boolean ignore; |
if (FailLowMemory(kMemForSndDoc)) { |
CloseResFile(resRef); //close it before leaving |
return(memFullErr); |
} |
theErr = noErr; |
newDocPtr = (SndDocPeek)NewPtrClear(sizeof(SndDocument)); |
if (newDocPtr != nil) { |
window = NewStackedWindow(rSoundWindow, (Ptr)newDocPtr); |
SetPort(window); |
if (! HasSoundInput()) |
SizeWindow(window, kSoundWindowSizeW, |
kSoundWindowSizeH - (kStadardWhiteSpacing + kSndButtonSizeH), false); |
TextFont(0); //set window to System font, blech |
AddSndDocControls(window); //add my buttons |
SetWRefCon((WindowPtr)newDocPtr, rSoundWindow); //mark as an app window |
SetWTitle((WindowPtr)newDocPtr, file->name); |
newDocPtr->resFile = resRef; //save its resource file ref |
newDocPtr->vRefNum = file->vRefNum; //save its volume reference |
newDocPtr->dirID = file->parID; //save its directory ID |
newDocPtr->sndInUse = false; //not yet it doesnÕt |
rHandle = (RectHandle)Get1Resource('RECT', rListRectID); |
if (rHandle != nil) { //get the stored list size |
lView = **rHandle; |
SetRect(&lBounds, 0, 0, 1, 0); //one dimentional list |
SetPt(&cSize, 0, 0); //List Mgr will find cell size |
list = LNew(&lView, &lBounds, cSize, 0, window, false, false, false, true); |
maxListHeight = window->portRect.bottom - window->portRect.top - (2 * kStadardWhiteSpacing); |
if (maxListHeight < (lView.bottom - lView.top)) |
maxListHeight = (lView.bottom - lView.top); |
if (list != nil) { |
newSize.h = (**list).cellSize.h; //get the width of one cell |
newSize.v = maxListHeight; //get the best height for all cells |
newSize.v -= maxListHeight % (**list).cellSize.v; |
LSize(newSize.h, newSize.v, list); //adjust for best scrolling |
(**list).selFlags = lOnlyOne; //single selections only |
newDocPtr->list = list; //save the list handle |
theErr = InitSndList(newDocPtr); //initialize the list data |
if (theErr == noErr) { |
SetPt(&aCell, 0, 0); //by default, I will... |
LSetSelect(true, aCell, newDocPtr->list); //select first item |
LSetDrawingMode(true, newDocPtr->list); |
ShowWindow((WindowPtr)newDocPtr); //get the show on the road |
if (FailLowMemory(0)) //if IÕm low on memory... |
theErr = memFullErr; |
} |
} |
else |
theErr = nilHandleErr; //list handle was NIL |
} |
else |
theErr = nilHandleErr; //RECT handle was NIL |
if (theErr != noErr) { //could not create the list |
ignore = DoCloseWindow(window); //if not, close the window |
newDocPtr = nil; //return nil pointer too |
} |
} |
else //NewPtrClear was nil |
theErr = MemError(); |
return(theErr); //return the error, if any |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Believe it or not, but this turned out to be one of the more difficult |
routines to write. This was the best approach I could thing of to avoid |
serious problems. I donÕt center the Standard File dialog because it |
wouldnÕt be under the File menu where the user probably has the mouse. I |
use OpenRFPerm because it allows for permissions and doesnÕt depend on the |
current directory. First problem with opening a resource file was to |
check if the file already being open. Read Tech Notes #116 and #185. |
GetFInfo seems to do the trick. At least it does report all files that |
are open on this machine. It may be incorrect for files on AppleShare |
opened by other machines. What IÕm worried about is when opening a |
resource file for the second time on the same machine may return the |
previous openerÕs resource map. This is very dangerous, and if the second |
opener calls CloseResFile there will be a crash. I want to be friendly |
and if the user tries to open the file the second time and IÕve already |
got it on the screen, then IÕll bring that window forward. To test for |
this, I compare the fileÕs name, DirID and vRefNum. The vRefNum is |
dynamic and needs to be converted into a volume name if I were to save |
this information for future use. |
There are a couple pitfalls, even with this method. The user can open the |
file, then move it to another directory. This same thing confuses all |
other applications IÕve tested. Opening a resource file will allocate a |
resource map handle into my heap, which may be large depending on the |
number of resources in that file. Additionally this loads all resources |
marked Òpreload.Ó So I set ResLoad to false to prevent the preloading. I |
test if the amount of memory I believe my document will require is |
available after opening the resource file. If true, I test if there are |
any sound resources in that file and if not alert the user. After all |
this, create the new document. If after creating the new document I find |
that memory is too low, will close it and return an error. If it does |
fail, then close the file before anything else or there may not be enough |
memory to show the alert. All of these tests created a number of |
IF-THEN-ELSE blocks and became unyielding. C programmers get a break, so |
gimme me one too. I use the MPW Pascal EXIT. |
BUG NOTE: DonÕt open a resource file that is already open. OpenResFile |
may return an existing resource map when it gets opWrErr from the file |
system. If this happens, the resource file will not be unique and this |
is very bad. Another problem is if I get a read-only path and someone |
else opens it for read/write. This is also very bad. Read Tech Notes |
#116 and #185 hint at this problem, but I think a more comprehensive one |
is in order. |
BUG NOTE: GetWDInfo fails with nsvErr if the working directory returned |
from Standard File is the root of an A/UX volume. I could work around |
this, but it would be dependant on the current version of A/UX. Read |
Tech Note #229. I believe this is also true for TOPS. |
VERSION 1.1: I'll open files that do not have snd resources in them. |
This allows users to open existing files and then record or paste a sound |
into it. I changed OpenRFPerm to HOpenResFile to avoid working |
directories and to be consistent with the rest of the sources. I |
separated the standard file code from OpenSndDoc to support opening |
documents being open from the Finder. This allows me to share the same |
routine to open files either from double clicking them in the Finder, or |
by the Standard File dialog. Removed the checking for low memory conditions |
since CreateSoundDoc is now doing this. |
*/ |
#pragma segment Open |
void OpenSoundDoc(FSSpecPtr file) |
{ |
HParamBlockRec fPBRec; |
WindowPtr window; |
short resFileRef; |
OSErr theErr; |
fPBRec.fileParam.ioCompletion = nil; //prepare a paramBlock |
fPBRec.fileParam.ioNamePtr = file->name; |
fPBRec.fileParam.ioVRefNum = file->vRefNum; |
fPBRec.fileParam.ioDirID = file->parID; |
fPBRec.fileParam.ioFVersNum = 0; |
fPBRec.fileParam.ioFDirIndex = 0; |
theErr = PBHGetFInfoSync(&fPBRec); //fPBRec on stack, synch only |
if (theErr == noErr) { |
if (fPBRec.fileParam.ioFlAttrib & kResForkOpenBit) { |
if (OpenByApp(file, &window)) |
SelectWindow(window); //I opened this one, select it |
else |
AlertUser(noErr, sCurInUseErr); //in use by someone else |
return; |
} |
} else { |
AlertUser(theErr, sStandardErr); //PBGetFInfo failed |
return; |
} |
SetResLoad(false); //donÕt load any resources |
resFileRef = FSpOpenResFile(file, fsCurPerm); |
theErr = ResError(); //save error, if any |
SetResLoad(true); //restore ResLoad state |
UseResFile(gAppResRef); //changes ResErr |
if (resFileRef != kResFileNotOpened) //error from OpenRFPerm? |
theErr = CreateSoundDoc(resFileRef, file); |
if (theErr != noErr) |
AlertUser(theErr, sNewDocErr); //couldnÕt create new doc |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: Create a new file for the user. I prefer to use the new |
HCreateResFile to avoid working directories, and to be consistent with |
the rest of the sources. Also, I need the real vRefNum and dirID to |
store in the sndDoc record. After creating the file, I set its Finder |
information to the proper type and creator. Finally, I get to open the |
new file. After this point everything is exactly like opening an |
existing file and creating a new sound document. |
VERSION 1.2: If dupFNErr is returned by HCreateResFile, then the user |
wants to replace the file with a new SoundApp document. |
*/ |
#pragma segment Open |
void NewSoundDoc(void) |
{ |
Str255 msg; |
StandardFileReply reply; |
StringHandle strHandle; |
short resFileRef; |
OSErr theErr; |
strHandle = (StringHandle)Get1Resource('STR ', rPutFileMsg); |
if (strHandle != nil) |
PStringCopy(*strHandle, msg); //save no name title |
else |
msg[0] = 0; //at least an empty string |
StandardPutFile(msg, "\p", &reply); |
if (reply.sfGood) |
{ |
theErr = noErr; |
if (reply.sfReplacing) |
theErr = FSpDelete(&reply.sfFile); //we're replacing it |
if (theErr == noErr) |
{ |
FSpCreateResFile(&reply.sfFile, rAppSignature, rSndAppDocType, reply.sfScript); |
theErr = ResError(); |
if (theErr == noErr) { |
resFileRef = FSpOpenResFile(&reply.sfFile, fsCurPerm); |
theErr = ResError(); //save error, if any |
UseResFile(gAppResRef); //changes ResErr |
if (resFileRef != kResFileNotOpened) //error from HOpenResFile |
theErr = CreateSoundDoc(resFileRef, &reply.sfFile); |
} |
} |
if (theErr != noErr) |
AlertUser(theErr, sNewDocErr); //return the error |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is much more than what a typical file filter might do but I wanted |
the user to easily find files that contained snd resources. So, the |
global flag gSndFilesOnly is used to determine when to filter for such |
files. Otherwise, I show all files and allow the application to report to |
the user the file theyÕve just tried to open doesnÕt have any sounds to |
play with. Opening resource forks can be tricky if itÕs already open. It |
would be very bad to use a resource fork that is already open by another |
application. The problem being that the Resource Manager doesnÕt deal |
with multiple users. Read Tech notes #116 and #185. ThereÕs also another |
problem. If a resource fork is opened by two applications and one closes |
the file then the entire resource fork may closed out from underneath the |
other application. I do, however, want to show the user files that |
contain sound resources even if they are currently open. I use |
HOpenResFile with read only permission, which will give me a unique |
resource reference. I look for a 'snd ' resource and then immediately |
close the file. Do not use a resource file opened with read only |
permission. Another reason to use HOpenResFile is to avoid a necessary |
working directory. I do not have one while in the file filter, but can |
use the DirID. Performing this search on each file is time consuming so I |
show a spinning cursor to show the user IÕm working. Opening a resource |
fork may load resources mark preload. To avoid this, I call SetResLoad |
to false. I bet you thought the Resource Manager was a free lunch. Ha! |
Read Tech Note #203 for other reasons not to play with resources. |
BUG NOTE: While debugging this routine using heap scramble, I found that |
OpenResFile would not open the requested file. IÕm not sure if this is a |
problem with OpenResFile or SFGetFile. |
VERSION 1.1: The bug mentioned above was found. The problem is that the |
paramBlock happens to be a re-locatable block in the heap. Passing the |
de-reference ioNamePtr to HOpenResFile was de-referencing this |
re-locatable paramBlock. Since HOpenResFile moves memory, the namePtr |
would no longer valid and thus HOpenResFile would fail. Now I copy the |
name out of the paramBlock and use the local variable in HOpenResFile. |
This problem was fixed in System 7, which no longer passes a re-locatable |
block to the file filter. The new version allows for any resource file |
to be opened. Because of this new feature, I only show the user files |
that have a resource fork. |
VERSION 1.2: This is a filter for System 7 now, and I don't show invisible files. |
*/ |
#pragma segment Open |
pascal Boolean SFFilter(CInfoPBPtr p, Boolean *sndFilesOnly) |
{ |
#define kShowIt false //false means I do not filter out... |
#define kDoNotShowIt true //the file and true means that I do. |
long oldTicks; |
short resRef; |
short curRes; |
Boolean result; |
oldTicks = TickCount(); |
result = kDoNotShowIt; //donÕt show anything until I say so |
if ( (*sndFilesOnly) && !(p->hFileInfo.ioFlFndrInfo.fdFlags & fInvisible) ) |
{ |
curRes = CurResFile(); |
SetResLoad(false); |
resRef = HOpenResFile(p->hFileInfo.ioVRefNum, p->hFileInfo.ioFlParID, |
p->hFileInfo.ioNamePtr, fsRdPerm); |
if (resRef != kResFileNotOpened) { |
UseResFile(resRef); |
if (Count1Resources(soundListRsrc) > 0) |
result = kShowIt; //hey, we found a sound in here |
CloseResFile(resRef); |
} //restore everything |
SetResLoad(true); |
UseResFile(curRes); |
} else |
{ |
if ( !(p->hFileInfo.ioFlFndrInfo.fdFlags & fInvisible) // if not invisible |
&& (p->hFileInfo.ioFlRLgLen > 0) ) // and has resources |
{ |
result = kShowIt; |
} |
} |
//RotateCursor(TickCount() - oldTicks); |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
#pragma segment Open |
pascal short SFGetHook(short mySFItem, DialogPtr dialog, void *sndFilesOnly) |
{ |
ControlHandle cntl; |
Rect r; |
short kind; |
short result; |
result = mySFItem; |
GetDialogItem(dialog, rSndOnlyCheckBox, &kind, (Handle *)&cntl, &r); |
switch (mySFItem) { |
case sfHookFirstCall: |
if (*(Boolean *)sndFilesOnly) |
SetControlValue(cntl, kCntlOn); |
else |
SetControlValue(cntl, kCntlOff); |
break; |
case rSndOnlyCheckBox: |
if (GetControlValue(cntl) == kCntlOff) { |
SetControlValue(cntl, kCntlOn); |
*(Boolean *)sndFilesOnly = true; |
} |
else { |
SetControlValue(cntl, kCntlOff); |
*(Boolean *)sndFilesOnly = false; |
} |
result = sfHookRebuildList; |
break; |
} |
return (result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: This routine used to be inside of OpenSoundDoc. The |
standard file code was removed to here in order to support opening |
documents being open from the Finder. I've switched to using a constant |
for the topLeft point of the Standard File dialog. It saves a few bytes |
of code, so what the hell? I found a cosmetic problem with the cursor |
not being restored back to arrow after returning Standard File if there |
was an error dialog shown immediately. So, it is immediately set back to |
the arrow. |
*/ |
#pragma segment Main |
void GetSoundDoc(void) |
{ |
StandardFileReply reply; |
SFTypeList typeList; //not used, just a placeholder |
Point sfTopLeft; |
Boolean sndFilesOnly; |
//SpinCursor(0); //get the spinning cursor ready |
sfTopLeft.v = -1; //-1,-1 means to center the dialog |
sfTopLeft.h = -1; |
sndFilesOnly = false; |
CustomGetFile((FileFilterYDUPP)GetRoutineAddress(SFFilter), -1, typeList, &reply, rCustomGetFileDLOG, |
sfTopLeft, GetRoutineAddress(SFGetHook), nil, nil, nil, &sndFilesOnly); |
SetCursor(&qd.arrow); |
if (reply.sfGood) |
OpenSoundDoc(&reply.sfFile); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Draw the contents of the about window in response to an update event. At |
this point, BeginUpdate has been called which sets the windowÕs visRgn to |
clip drawing only where it needs to be done. I have some text to draw, an |
icon, a picture, and a default button with its outline. If I have a color |
icon handle IÕll call PlotCIcon. I offset the picture to draw to the |
right of the icon. The text will appear in a rectangle as large as the |
window, but below the icon and above the button. This allows me the |
change the text and if needed change the size of the windowÕs rectangle to |
compensate. I wonÕt have to recompile any code. Some people use dialogs |
because of this, but IÕm demonstrating how it can be done without them. I |
use UpdateControls to avoid needless drawing that happens with DrawControls. |
It not only runs faster but doesnÕt flicker. |
VERSION 1.2: Uses System 7's PlotIcon to draw the proper icon. |
*/ |
#pragma segment Main |
void DrawAboutWindow(WindowPtr window) |
{ |
Rect theRect; |
OSErr ignoreErr; |
PenNormal(); |
SetRect(&theRect, kStdAlertIconLeft, kStdAlertIconTop, kStdAlertIconRight, kStdAlertIconBottom); |
ignoreErr = PlotIconID(&theRect, atNone, ttNone, rMoofIcon); |
theRect = (**(PicHandle)((AboutWPeek)window)->appPict).picFrame; |
OffsetRect(&theRect, -theRect.left + kStdAlertIconRight + kStadardWhiteSpacing, |
-theRect.top + kStdAlertIconTop); |
DrawPicture((PicHandle)((AboutWPeek)window)->appPict, &theRect); |
SetRect(&theRect, window->portRect.left, kStdAlertIconBottom, |
window->portRect.right, |
window->portRect.bottom - kDafaultButSizeH); |
InsetRect(&theRect, kStadardWhiteSpacing, kStadardWhiteSpacing); |
HLock(((AboutWPeek)window)->comment); |
TETextBox(*(((AboutWPeek)window)->comment) + 1, |
StrLength(*(((AboutWPeek)window)->comment)), &theRect, teJustLeft); |
HUnlock(((AboutWPeek)window)->comment); |
UpdateControls(window, window->visRgn); |
DoButtonOutline((ControlHandle)((WindowPeek)window)->controlList); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
First thing to do is call KillSound to stop any sound in progress and |
close the status window. If thereÕs enough memory available, IÕll play |
the about sound. This is sound is asynchronous and will automatically be |
disposed of when completed. There is an icon, taken from the bundle |
resources, and a picture. The text is taken from a string resource. It |
contains a couple variables just like ParamText would want. I get the |
current name of the application from the AppParms. This is the name of |
the application as specified by the user. I read in my 'vers' resource |
to get the current version to be displayed in the window. Read Tech Note |
189 for more details on the vers resource. I think these are two |
important things to show in the about box. These two portions of the text |
are put into place with my own version of ParamText. I tried all of this |
with a standard dialog, but had trouble. Sometimes the color icon didnÕt |
show up. Trying to center justify text with a statText item wasnÕt really |
possible. On the Mac SE, for some unknown reason, TESetJust failed to |
center the textH of the dialog. I also found that this call would set the |
dialogÕs text back to Chicago even after I had called TextFont. So, I |
gave up and did everything myself. This is a demonstration of how to |
create a dialog without using the Dialog Manager. |
*/ |
#pragma segment Main |
void DoAbout(void) |
{ |
Str255 verNum; |
Str255 appName; |
Ptr aboutPtr; |
AboutWPeek aboutPeek; |
ControlHandle control; |
VersRecHndl curVersion; |
OSErr theErr; |
Boolean ignore; |
KillSound(); |
aboutPtr = NewPtrClear(sizeof(AboutWindow)); |
if (aboutPtr != nil) { |
aboutPeek = (AboutWPeek)GetNewWindow(rAboutWindow, aboutPtr, (WindowPtr)-1); |
SetWRefCon((WindowPtr)aboutPeek, rAboutWindow); |
curVersion = (VersRecHndl)Get1Resource('vers', 1); |
if (curVersion != nil) |
PStringCopy((**curVersion).shortVersion, verNum); // get version string |
else |
verNum[0] = 0; // at least initialize it |
PStringCopy(LMGetCurApName(), appName); |
aboutPeek->appPict = Get1Resource('PICT', rAppPict); |
aboutPeek->comment = Get1Resource('STR ', rAboutText); |
control = GetNewControl(rAboutOkCntl, (WindowPtr)aboutPeek); |
if ( (aboutPeek->appPict != nil) |
&& (aboutPeek->comment != nil) |
&& (control != nil)) |
{ |
HNoPurge(aboutPeek->appPict); // must keep them around |
HNoPurge(aboutPeek->comment); |
MyParamText((StringHandle)aboutPeek->comment, appName, verNum); |
if (!FailLowMemory(0)) |
theErr = AsynchSndPlay((SndListHandle)Get1Resource(soundListRsrc, rMoofSound)); |
} |
else // couldnÕt build window |
ignore = DoCloseWindow((WindowPtr)aboutPeek); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Get the selected sound and pass it to AsynchSndPlay or HyperSndPlay. This |
depends on the normal flag. HyperCardÕs method is not normal. This is |
the HyperCard version of playing a sound. It resamples any sound to |
middle C. If the resource is too large to fit in memory, IÕll get a nil |
handle error from the SoundUnit. It might be nice to test for this |
before calling the SoundUnit and telling the user theyÕre too low on |
memory, but the SoundUnit is robust enough and reports the error. ItÕs |
important to call KillSound to dispose of any data that was |
allocated if an error were to occur. |
*/ |
#pragma segment Main |
void PlaySelectedSnd(SndDocPeek sndDoc, short message) |
{ |
SndListHandle sndHandle; |
OSErr theErr; |
theErr = GetSelection(sndDoc, &sndHandle); |
if (theErr == noErr) { |
if (message == sPlayingMsg) |
theErr = AsynchSndPlay(sndHandle); |
else |
theErr = HyperSndPlay(sndHandle); |
} |
if (theErr == noErr) { |
sndDoc->sndInUse = true; // this document has a snd in use |
ShowStatusWindow(message); |
} |
else { |
KillSound(); |
AlertUser(theErr, sSoundErr); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given a sound resource ID, this will get my song resource and call the |
SoundUnit to use that sampled sound and song to play a tune. This |
application will play one of two songs for sampled sounds. If any error |
is encountered, I call KillSound to dispose of all data. |
*/ |
#pragma segment Main |
void PlaySndSong(SndDocPeek sndDoc, short sndID) |
{ |
SndChannelPtr chan; |
SndListHandle sndInst; |
SndListHandle sndSong; |
OSErr theErr; |
theErr = GetSelection(sndDoc, &sndInst); |
if (theErr == noErr) { |
sndSong = (SndListHandle)Get1Resource(soundListRsrc, sndID); |
theErr = ResError(); // save any error |
if (sndSong != nil) { |
theErr = GetSampleChan(&chan, kInitNone, sndInst); |
if (theErr == noErr) { |
sndDoc->sndInUse = true; // this document has a snd in use |
theErr = PlaySong(chan, sndSong); |
} |
} |
} |
if (theErr == noErr) { |
if (sndID == rScaleSnd) |
ShowStatusWindow(sScaleMsg); |
else // I can play scales or a melody |
ShowStatusWindow(sMelodyMsg); |
} |
else { // catch any errors |
KillSound(); |
AlertUser(theErr, sSoundErr); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given a sound resource ID, this will get my song resource and call the |
SoundUnit to use the note synthesizer and song to play a tune. I request |
the note channelÕs timbre (sounds like Òtom burrÓ). If any error is |
encountered, I call KillSound to dispose of all data. |
*/ |
#pragma segment Main |
void PlaySquareSong(short sndID) |
{ |
SndListHandle sndSong; |
SndChannelPtr chan; |
OSErr theErr; |
sndSong = (SndListHandle)Get1Resource(soundListRsrc, sndID); |
if (sndSong != nil) { |
theErr = GetSquareWaveChan(&chan, kPreferredTimbre); |
if (theErr == noErr) { |
theErr = PlaySong(chan, sndSong); |
if (theErr == noErr) { |
if (sndID == rScaleSnd) |
ShowStatusWindow(sScaleMsg); |
else // I play scales or a melody |
ShowStatusWindow(sMelodyMsg); |
} |
} |
if (theErr != noErr) { // catch any errors |
KillSound(); |
AlertUser(theErr, sSoundErr); |
} |
} |
else |
AlertUser(ResError(), sResErr); // IÕll return the resource error |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is a demonstration of the note synthÕs tone qualities (or the lack |
there of). This simply loops through timbres sending alternating note and |
rest commands. Once all the notes have been sent, then I need to send a |
callBackCmd to signal the SoundUnit to dispose of the channel. Well, |
actually the SoundUnit will set a global flag that the application will be |
polling for later in the event loop. Once this happens, or if any errors |
are encountered along the way, KillSound will be called. One very |
disappointing aspect to changing the timbre is that the Mac Plus or SE |
cannot handle this while a note is sounding. The Apple Sound Chip can and |
if you wanted to remove the rests try this routine on a Mac II, for |
example, you can hear a continuous note while the timbre changes. Try |
this on a Mac Plus and youÕll hear garbage. IÕd show this myself, but how |
do I determine if the Mac has the ASC? |
BUG NOTE: There is problem when the final sound command is a freqDurationCmd. |
The note will continue to sound, looping forever, until a quietCmd is sent |
or the channel is disposed of. To prevent unwanted looping, I send a |
quietCmd after all notes. |
*/ |
#pragma segment Main |
void PlaySquareTimbres(void) |
{ |
SndChannelPtr chan; |
short timbre; |
OSErr theErr; |
theErr = GetSquareWaveChan(&chan, kPreferredTimbre); |
if (theErr == noErr) { |
ShowStatusWindow(sTimbresMsg); |
for (timbre = kSineWave; timbre <= kSquareWave; timbre += 8) { |
theErr = SetSquareWaveTimbre(chan, timbre, kWait); |
if (theErr == noErr) |
theErr = SendNote(chan, kOneSecond / 2, kOctave7 + Akey); |
if (theErr == noErr) |
theErr = SendRest(chan, kOneSecond / 10); |
timbre = timbre + 8; // skip a few more timbres |
if (theErr != noErr) // if there was an error... |
break; // get out of the loop |
} |
} |
if (theErr == noErr) |
theErr = SoundComplete(chan); |
else { // catch any errors |
KillSound(); |
AlertUser(theErr, sSoundErr); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This demonstrates the wave table synthesizers. First thing to do is |
obtain a wave cycle and the 'snd ' resource containing a song to be |
played. Then IÕm ready to get the four wave channels and install the |
chosen wave. Once the wave is installed into the channel I can dispose of |
the memory used to create the wave, since the Sound Manager will copy it |
to its internal buffers. At this point IÕm ready to play the song. ItÕs |
not easy to hear four wave synths playing the same note with the same wave |
table. I was going to add a modifier to the channel that would transpose |
each channels note, but I found a bug. |
BUG NOTE: Installing a modifier to one of the wave table channels caused |
the channel to fail. My normal sequence of events is this: I synchronize |
the four channels, send all the note commands, end this with the |
callBackCmd, and finally release the channels to play their queues. When |
I added a modifier, the callBackCmd was the first command to be processed. |
This caused my completion routines to be called and before the channel |
made any sound it was disposed. I tried this without the callBackCmd and |
the channel never processed any commands. |
BUG NOTE: The wave table synthesizer is not working on a non-Apple Sound |
chip based Mac. At least not so far with any System 6.0x releases. This |
leaves the Mac Plus/SE without four tone polyphonic sound. |
VERSION 1.2: No longer need to call HasWorkingWaveTables since Sound Manager |
2 and later fixed the problem. |
*/ |
#pragma segment Main |
void PlayWaveScale(void) |
{ |
SndChannelPtr chan1, chan2, chan3, chan4; |
SndListHandle sndSong; |
SndListHandle waveHandle; |
Ptr waveTablePtr; |
long offSet; |
short sndType; |
short waveLength; |
OSErr theErr; |
sndSong = (SndListHandle)Get1Resource(soundListRsrc, rScaleSnd); |
if (sndSong != nil) { |
waveHandle = (SndListHandle)Get1Resource(soundListRsrc, rTenorVox); |
theErr = HoldSnd(waveHandle); |
if (theErr == noErr) { |
offSet = GetSndDataOffset(waveHandle, &sndType, &waveLength); |
waveTablePtr = (Ptr)((long)*waveHandle + offSet); |
theErr = GetWaveChans(&chan1, &chan2, &chan3, &chan4); |
if (theErr == noErr) { |
theErr = InstallWave(chan1, waveTablePtr, waveLength); |
if (theErr == noErr) { |
theErr = InstallWave(chan2, waveTablePtr, waveLength); |
if (theErr == noErr) { |
theErr = InstallWave(chan3, waveTablePtr, waveLength); |
if (theErr == noErr) |
theErr = InstallWave(chan4, waveTablePtr, waveLength); |
} |
} |
} |
HUnlock((Handle)waveHandle); |
HPurge((Handle)waveHandle); |
if (theErr == noErr) |
theErr = Play4ChanSongs(chan1, chan2, chan3, chan4, |
sndSong, sndSong, sndSong, sndSong); |
if (theErr == noErr) |
ShowStatusWindow(sScaleMsg); |
else { // catch any SoundUnit errors |
KillSound(); |
AlertUser(theErr, sSoundErr); |
} |
} |
else |
AlertUser(theErr, sResErr); // couldnÕt get waveHandle |
} |
else |
AlertUser(ResError(), sResErr); // couldnÕt get song |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is a utility routine used to obtain the four snd resources that |
contain parts of a song. Nothing much to do other then get the resource |
and do error checking. |
*/ |
#pragma segment Main |
OSErr GetSndSongs(short sndSongID1, short sndSongID2, |
short sndSongID3, short sndSongID4, |
SndListHandle *sndSong1, SndListHandle *sndSong2, |
SndListHandle *sndSong3, SndListHandle *sndSong4) |
{ |
OSErr result = noErr; // initialize result |
// get all the snds |
*sndSong1 = (SndListHandle)Get1Resource(soundListRsrc, sndSongID1); |
if (sndSong1 != nil) { |
*sndSong2 = (SndListHandle)Get1Resource(soundListRsrc, sndSongID2); |
if (sndSong2 != nil) { |
*sndSong3 = (SndListHandle)Get1Resource(soundListRsrc, sndSongID3); |
if (sndSong3 != nil) |
*sndSong4 = (SndListHandle)Get1Resource(soundListRsrc, sndSongID4); |
} |
} |
if ((sndSong1 == nil) || (sndSong2 == nil) |
|| (sndSong3 == nil) || (sndSong4 == nil)) |
result = ResError(); // return resource error |
return (result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This routine is a utility IÕm using to prepare four wave table channels. |
First thing to do is obtain a wave table data I stored in a set of 'snd ' |
resources. Then IÕm ready to install these wave tables into the four wave |
channels. Once the waves are installed into the channel I can dispose of |
the memory used to create each wave, since the Sound Manager will copy |
them to its internal buffers. At this point the channels are ready to |
play sounds. |
*/ |
#pragma segment Main |
OSErr InstallWaveSnds(SndChannelPtr chan1, SndChannelPtr chan2, |
SndChannelPtr chan3, SndChannelPtr chan4, |
short waveID1, short waveID2, short waveID3, short waveID4) |
{ |
SndListHandle waveSnd1, waveSnd2, waveSnd3, waveSnd4; |
Ptr wavePtr; |
long offSet; |
short waveLgth; |
short sndType; |
OSErr theErr; |
// get em and hold em down |
waveSnd1 = (SndListHandle)Get1Resource(soundListRsrc, waveID1); |
theErr = HoldSnd(waveSnd1); |
if (theErr == noErr) { |
waveSnd2 = (SndListHandle)Get1Resource(soundListRsrc, waveID2); |
theErr = HoldSnd(waveSnd2); |
if (theErr == noErr) { |
waveSnd3 = (SndListHandle)Get1Resource(soundListRsrc, waveID3); |
theErr = HoldSnd(waveSnd3); |
if (theErr == noErr) { |
waveSnd4 = (SndListHandle)Get1Resource(soundListRsrc, waveID4); |
theErr = HoldSnd(waveSnd4); |
} |
} |
} |
if (theErr != noErr) |
return (theErr); // weÕre out of here |
// catch the waves |
offSet = GetSndDataOffset(waveSnd1, &sndType, &waveLgth); |
wavePtr = (Ptr)((long)*waveSnd1 + offSet); |
theErr = InstallWave(chan1, wavePtr, waveLgth); |
if (theErr == noErr) { |
offSet = GetSndDataOffset(waveSnd2, &sndType, &waveLgth); |
wavePtr = (Ptr)((long)*waveSnd1 + offSet); |
theErr = InstallWave(chan2, wavePtr, waveLgth); |
if (theErr == noErr) { |
offSet = GetSndDataOffset(waveSnd3, &sndType, &waveLgth); |
wavePtr = (Ptr)((long)*waveSnd1 + offSet); |
theErr = InstallWave(chan3, wavePtr, waveLgth); |
if (theErr == noErr) { |
offSet = GetSndDataOffset(waveSnd4, &sndType, &waveLgth); |
wavePtr = (Ptr)((long)*waveSnd1 + offSet); |
theErr = InstallWave(chan4, wavePtr, waveLgth); |
} |
} |
} |
HUnlock((Handle)waveSnd1); // done with resources |
HPurge((Handle)waveSnd1); |
HUnlock((Handle)waveSnd2); |
HPurge((Handle)waveSnd2); |
HUnlock((Handle)waveSnd3); |
HPurge((Handle)waveSnd3); |
HUnlock((Handle)waveSnd4); |
HPurge((Handle)waveSnd4); |
return (theErr); // return the error |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is the demonstration of the wave table synthesizers. This will get |
four snd resources that contain parts to a song. Then I get the four wave |
table channels. With these four channels, I install two other snd |
resources that contain wave table data. Once the four songs, four |
channels, and two wave tables are ready then I play the song. |
BUG NOTE: The wave table synthesizer is not working on a non-Apple Sound |
chip based Mac. At least not so far with any System 6.0x release. This |
leaves the Mac Plus/SE without four tone polyphonic sound. |
VERSION 1.2: No longer need to call HasWorkingWaveTables since Sound Manager |
2 and later fixed the problem. |
*/ |
#pragma segment Main |
void PlayWaveMelody(void) |
{ |
SndListHandle sndSong1, sndSong2, sndSong3, sndSong4; |
SndChannelPtr chan1, chan2, chan3, chan4; |
OSErr theErr; |
theErr = GetSndSongs(rMelodyPart1, rMelodyPart2, rMelodyPart3, rMelodyPart4, |
&sndSong1, &sndSong2, &sndSong3, &sndSong4); |
if (theErr != noErr) { |
AlertUser(theErr, sResErr); // return the error |
return; // weÕre out of here |
} |
theErr = GetWaveChans(&chan1, &chan2, &chan3, &chan4); |
if (theErr == noErr) { |
theErr = InstallWaveSnds(chan1, chan2, chan3, chan4, |
rWaveMelody, rWaveHarmony, rWaveHarmony, rWaveHarmony); |
if (theErr == noErr) |
theErr = Play4ChanSongs(chan1, chan2, chan3, chan4, |
sndSong1, sndSong2, sndSong3, sndSong4); |
} |
if (theErr == noErr) |
ShowStatusWindow(sMelodyMsg); |
else { // catch any errors |
KillSound(); |
AlertUser(theErr, sSoundErr); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is the demonstration of the wave table synthesizers. This will get |
four snd resources that contain parts to a song. Then I get the four wave |
table channels. With these four channels, I install four other snd resources |
that contain wave table data. Once the four songs, four channels, and |
four wave tables are ready then I play the song. By the way, SATB stands |
for Soprano, Alto, Tenor, and Bass. ItÕs standard music-speak. |
BUG NOTE: The wave table synthesizer is not working on a non-Apple Sound |
chip based Mac. At least not so far with any System 6.0x release. This |
leaves the Mac Plus/SE without four tone polyphonic sound. |
VERSION 1.2: No longer need to call HasWorkingWaveTables since Sound Manager |
2 and later fixed the problem. |
*/ |
#pragma segment Main |
void PlayWaveSATB(void) |
{ |
SndListHandle sndSong1, sndSong2, sndSong3, sndSong4; |
SndChannelPtr chan1, chan2, chan3, chan4; |
OSErr theErr; |
theErr = GetSndSongs(rCounterPt1, rCounterPt2, rCounterPt3, rCounterPt4, |
&sndSong1, &sndSong2, &sndSong3, &sndSong4); |
if (theErr != noErr) { |
AlertUser(theErr, sResErr); // return the error |
return; // weÕre out of here |
} |
theErr = GetWaveChans(&chan1, &chan2, &chan3, &chan4); |
if (theErr == noErr) { |
theErr = InstallWaveSnds(chan1, chan2, chan3, chan4, |
rSopranoVox, rAltoVox, rTenorVox, rBassVox); |
if (theErr == noErr) |
theErr = Play4ChanSongs(chan1, chan2, chan3, chan4, |
sndSong1, sndSong2, sndSong3, sndSong4); |
} |
if (theErr == noErr) |
ShowStatusWindow(sCounterPtMsg); |
else { // catch any errors |
KillSound(); |
AlertUser(theErr, sSoundErr); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.2: Create four sample sound channels with an instrument sound |
installed in each one. Then play the four part song to demonstrate the |
sample sound channel. |
*/ |
#pragma segment Main |
void PlaySampleSATB(void) |
{ |
SndListHandle sndSong1, sndSong2, sndSong3, sndSong4; |
SndListHandle sampleHarmony; |
SndChannelPtr chan1, chan2, chan3, chan4; |
OSErr theErr; |
theErr = GetSndSongs(rCounterPt1, rCounterPt2, rCounterPt3, rCounterPt4, |
&sndSong1, &sndSong2, &sndSong3, &sndSong4); |
if (theErr != noErr) { |
AlertUser(theErr, sResErr); // return the error |
return; // weÕre out of here |
} |
sampleHarmony = (SndListHandle)Get1Resource(soundListRsrc, rSampleHarmony); |
theErr = Get4SampleInstruments(&chan1, &chan2, &chan3, &chan4, |
sampleHarmony, sampleHarmony, sampleHarmony, sampleHarmony); |
if (theErr == noErr) |
theErr = Play4ChanSongs(chan1, chan2, chan3, chan4, |
sndSong1, sndSong2, sndSong3, sndSong4); |
if (theErr == noErr) |
ShowStatusWindow(sCounterPtMsg); |
else // catch any errors |
{ |
KillSound(); |
AlertUser(theErr, sSoundErr); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.2: Create four sample sound channels with an instrument sound |
installed in each one. Then play the four part song to demonstrate the |
sample sound channel. |
*/ |
#pragma segment Main |
void PlaySampleMelody(void) |
{ |
SndListHandle sndSong1, sndSong2, sndSong3, sndSong4; |
SndListHandle sampleHarmony, sampleMelody; |
SndChannelPtr chan1, chan2, chan3, chan4; |
OSErr theErr; |
theErr = GetSndSongs(rMelodyPart1, rMelodyPart2, rMelodyPart3, rMelodyPart4, |
&sndSong1, &sndSong2, &sndSong3, &sndSong4); |
if (theErr != noErr) { |
AlertUser(theErr, sResErr); // return the error |
return; // weÕre out of here |
} |
sampleHarmony = (SndListHandle)Get1Resource(soundListRsrc, rSampleHarmony); |
sampleMelody = (SndListHandle)Get1Resource(soundListRsrc, rSampleMelody); |
theErr = Get4SampleInstruments(&chan1, &chan2, &chan3, &chan4, |
sampleMelody, sampleHarmony, sampleHarmony, sampleHarmony); |
if (theErr == noErr) |
theErr = Play4ChanSongs(chan1, chan2, chan3, chan4, |
sndSong1, sndSong2, sndSong3, sndSong4); |
if (theErr == noErr) |
ShowStatusWindow(sMelodyMsg); |
else // catch any errors |
{ |
KillSound(); |
AlertUser(theErr, sSoundErr); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: This is just one of the reasons I hate the Dialog Manager. |
I needed a simple dialog to ask the user for a name for their new sound. |
To do this, I only needed a simple dialog with two buttons and an |
editable text item. Since the ModalDialog will not draw an outline |
around the default item, I then needed an userItem. This is the really |
stupid part. The userItem is only there just to get a chance to draw an |
outline around the default button. It has no other purpose. It is not |
visible, not does it get any user interaction what so ever. It's just a |
pain in the ass. |
*/ |
#pragma segment Main |
pascal void DefaultOutline(WindowPtr window, short theItem) |
{ |
#pragma unused (theItem) |
Handle itemHndl; |
Rect itemRect; |
short kind; |
GetDialogItem((DialogPtr)window, ok, &kind, &itemHndl, &itemRect); |
DoButtonOutline((ControlHandle)itemHndl); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: Show a dialog asking the user to name their new sound |
resource. I need to outline the default button, so I have to install an |
userItem setting its drawing procedure to do the outline around a |
different item altogether. The Dialog Manager really bugs me. |
*/ |
#pragma segment Main |
void GetSndName(Str255 sndName) |
{ |
DialogPtr dialog; |
Handle itemHndl; |
Rect itemRect; |
short kind; |
short theItem; |
dialog = GetNewDialog(rGetNameDLOG, nil, (WindowPtr)-1); |
GetDialogItem(dialog, rUserItem, &kind, &itemHndl, &itemRect); |
SetDialogItem(dialog, rUserItem, kind, (Handle)DefaultOutline, &itemRect); |
do |
ModalDialog(nil, &theItem); |
while ((theItem != ok) && (theItem != cancel)); |
if (theItem == ok) { |
GetDialogItem(dialog, rNameItem, &kind, &itemHndl, &itemRect); |
GetDialogItemText(itemHndl, sndName); |
} else |
sndName[0] = 0; |
DisposeDialog(dialog); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: This routine adds the given snd resource to the file and |
document. First thing to do is find a proper resource ID for the 'snd '. |
There is a reserved range for them, didn't you read the documentation? |
Once this new resource is added then I update the file and re-build the |
list of sounds. This is necessary since the list must be in the same |
order as the sounds are in the file. I ignore the error returned by |
InitSndList, since the chances of it failing at this point are slim. If |
anything is wrong with the list, then the rest of this application is |
robust enough to handle a resource problem. The user would have to close |
the document and attempt to open it again. If adding the resources |
works, but updating the file fails then I will remove it. This keeps the |
document consistent with the file. |
*/ |
#pragma segment Main |
OSErr AddSnd(SndDocPeek sndDoc, StringPtr sndNamePtr, SndListHandle sndHndl) |
{ |
short resID; |
OSErr theErr; |
short ignore; |
UseResFile(sndDoc->resFile); //put our resource in the right file |
do |
resID = Unique1ID(soundListRsrc); |
while (resID < kSystemSndRange); |
AddResource((Handle)sndHndl, soundListRsrc, resID, sndNamePtr); |
theErr = ResError(); |
UseResFile(gAppResRef); //restore our resource file |
if (theErr == noErr) { |
UpdateResFile(sndDoc->resFile); //update the file |
theErr = ResError(); |
if (theErr == noErr) { |
ignore = FlushVol(nil, sndDoc->vRefNum); |
LDelRow(0, 0, sndDoc->list); //delete all the rows |
ignore = InitSndList(sndDoc); //re-create the list |
SelectSndCell(sndDoc, resID); //select the new snd |
} else |
RemoveResource((Handle)sndHndl); //couldn't add it, update failed |
} |
return(theErr); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: This routine has one tricky aspect involving the Resource |
Manager and removing a resource. There's a catch. I want the document |
to always reflect the contents of the file on disk. In other words, |
InitSndList depends on UpdateResFile returning noErr. If the file is |
locked, then removing a resource shouldn't be allowed but RmveResource |
will return noErr even if the file cannot be updated. This is a |
"feature" since the user may unlock the volume after removing the |
resource and then UpdateResFile would work. If RmveResource succeeds, |
but UpdateResFile fails then the resource map in memory will be |
inconsistent with what's on disk. If the user then closed the document |
and opened it again, the resource they just removed would magically still |
be there. The basic problem is that it is difficult to determine if a |
given file will really allow write access. There's at least three |
situations to check: a locked file, a locked volume, or an AppleShare |
folder privileges issue. The ideal, and probably the best, solution |
would be to deselect the edit commands from the user for a read-only |
file. This would involve lots of File Manager calls every time the user |
selects a menu command. (If you thought that GetFCBInfo would tell you |
this, you're wrong. You can have write permission to a file that is on a |
locked volume.) Additionally, the user needs to see that the reason the |
edit menu doesn't work is because the file is read-only which would |
require another feature to be added with lots more code. This is why I |
call ChangedResource before any other Resource Manager calls. |
ChangedResource will determine if the resource can be written to disk. |
If not, then it returns an error which is exactly what I wanted to know |
in the first place. I want to flush the cache to keep the disk's |
resource map consistent with the resource data. Otherwise, a crash could |
occur the resource fork might be damaged and the file has to be repaired |
(if possible) or deleted. I'm ignoring the InitSndList result. At this |
stage, there's only a slim chance the list couldn't be re-built. If it |
does fail, the file should be closed. |
A tip to the reader: dispose of as much memory as possible before calling |
UpdateResFile. This makes it faster. UpdateResFile will not purge any |
memory, but only attempts to use the available space. If there's little |
free space then UpdateResFile will run really slow. |
*/ |
#pragma segment Main |
void ClearSnd(SndDocPeek sndDoc) |
{ |
SndListHandle sndHndl; |
OSErr theErr; |
short ignore; |
theErr = GetSelection(sndDoc, &sndHndl); //get the resource to remove |
if (theErr == noErr) { |
ChangedResource((Handle)sndHndl); //can we change the file? |
theErr = ResError(); //save the error result |
if (theErr == noErr) { |
UseResFile(sndDoc->resFile); //use the right resource file |
RemoveResource((Handle)sndHndl); //remove that sucker |
theErr = ResError(); //save the error result |
UseResFile(gAppResRef); //restore our resource file |
if (theErr == noErr) { |
DisposeHandle((Handle)sndHndl); //get rid of the memory |
UpdateResFile(sndDoc->resFile); //update the file |
theErr = ResError(); //save the error result |
if (theErr == noErr) |
ignore = FlushVol(nil, sndDoc->vRefNum); |
LDelRow(0, 0, sndDoc->list); //delete all the rows |
ignore = InitSndList(sndDoc); //re-create the list |
SelectNextCell(sndDoc->list, true); //select the first cell |
ActivateSndCntls(sndDoc); //may not be any sounds left |
} |
} |
} |
if (theErr != noErr) |
AlertUser(theErr, sEditErr); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: This routine will create a handle and record sound into it. |
A new sound will prompt the user for a name to add it to the document. |
The Sound Input Manager will re-size this handle to be as small as |
possible to contain only the samples recorded. You can pass sndHandl == |
nil to SndRecord, which will cause the Sound Input Manager to create a |
handle for you. If adding the new sound is successful, the new resource |
handle is marked purgeable, because I only use them temporally and they |
tend to be large. I have to set the sndHandle variable to nil in this |
case since it is now belongs to the Resource Manager and I don't want to |
dispose of it in the error handling code. The choice of recording |
quality wasn't given to the user. Typically, users would only be |
confused by the question of "what compression ratio do you prefer?" As |
a power user option, it would be nice to let the user set the rate. Be |
careful since this is a user interface issue, and Apple expects to see |
lots of Sound Input features in applications. |
*/ |
#pragma segment Main |
void DoRecordSound(SndDocPeek sndDoc) |
{ |
Str255 sndName; |
Point recTopLeft; |
long total; |
long contig; |
SndListHandle sndHandle; |
OSErr theErr; |
KillSound(); |
PurgeSpace(&total, &contig); |
sndHandle = (SndListHandle)NewHandle(contig - kMinSpace); |
if (sndHandle != nil) { |
recTopLeft.v = kRecordTop; |
recTopLeft.h = kRecordLeft; |
theErr = SndRecord(nil, recTopLeft, siBestQuality, &sndHandle); |
if (theErr == noErr) { |
GetSndName(sndName); |
theErr = AddSnd(sndDoc, sndName, sndHandle); |
if (theErr == noErr) { |
HPurge((Handle)sndHandle); |
sndHandle = nil; |
} |
} |
} else |
theErr = MemError(); |
if (sndHandle != nil) |
DisposeHandle((Handle)sndHandle); |
if ((theErr != noErr) && (theErr != userCanceledErr)) |
AlertUser(theErr, sSoundErr); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Enable and disable menus based on the current state. The user can only |
select enabled menu items so I set up all the menu items before calling |
MenuSelect or MenuKey, since these are the only times that a menu item |
can be selected. Note that MenuSelect is also the only time the user will |
see menu items. This approach to deciding what enable/disable state a menu |
item has the advantage of concentrating all the decision making in one |
routine, as opposed to being spread throughout the application. Other |
application designs may take a different approach that may or may not be |
just as valid. |
*/ |
#pragma segment Main |
void AdjustMenus(void) |
{ |
MenuHandle menu; |
WindowPtr window; |
Boolean allowEdit; |
window = FrontWindow(); |
menu = GetMenuHandle(mFile); //the File menu and items |
if (IsDAWindow(window) || IsDocWindow(window)) |
EnableItem(menu, iClose); |
else |
DisableItem(menu, iClose); |
menu = GetMenuHandle(mEdit); //the Edit menu and items |
allowEdit = IsDAWindow(window); |
if (allowEdit) //check the undo item |
EnableItem(menu, iUndo); |
else |
DisableItem(menu, iUndo); |
if (IsDocWindow(window)) { //handle the other edit items |
allowEdit = HasSelection((SndDocPeek)window) || allowEdit; |
EnableItem(menu, iPaste); |
} else |
DisableItem(menu, iPaste); |
if (allowEdit) { |
EnableItem(menu, iCut); |
EnableItem(menu, iCopy); |
EnableItem(menu, iClear); |
} else { |
DisableItem(menu, iCut); |
DisableItem(menu, iCopy); |
DisableItem(menu, iClear); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: Get the currently selected item and copy it to the |
clipboard. I want to also save the name for this resource, so I add a |
string type to the clipboard as well. |
*/ |
#pragma segment Main |
void CopySnd(SndDocPeek sndDoc) |
{ |
Str255 sndName; |
ResType rType; |
SndListHandle sndHndl; |
long scrapLen; |
short id; |
OSErr theErr; |
theErr = GetSelection(sndDoc, &sndHndl); |
if (theErr == noErr) { |
GetResInfo((Handle)sndHndl, &id, &rType, sndName); |
scrapLen = ZeroScrap(); //ignoring the result |
theErr = PutScrap(StrLength(sndName) + 1, 'STR ', sndName); |
HLock((Handle)sndHndl); |
theErr = PutScrap(GetHandleSize((Handle)sndHndl), soundListRsrc, (Ptr)(*sndHndl)); |
HUnlock((Handle)sndHndl); |
HPurge((Handle)sndHndl); |
} |
if (theErr != noErr) |
AlertUser(theErr, sEditErr); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: Copy the selection and then delete it. |
*/ |
#pragma segment Main |
void CutSnd(SndDocPeek sndDoc) |
{ |
CopySnd(sndDoc); |
ClearSnd(sndDoc); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
VERSION 1.1: This routine will create a new resource into the file |
containing the clipboard's sound data. A name is attempted to be found |
by first checking for the string type in the scrap. This application |
will always put both a string and a sound together on the clipboard. But |
if the user copied a sound from the Sound cdev, ResEdit, or by some other |
method then I'll ask the user for a new name. If adding the new resource |
is successful, then I mark the resource as purgeable and set the sndHndl |
variable to NIL. The Resource Manager then owns the handle and I don't |
want my error handling to dispose of this new handle. |
*/ |
#pragma segment Main |
void PasteSnd(SndDocPeek sndDoc) |
{ |
Str255 sndName; |
StringHandle sndNameHndl; |
long offset; |
long scrapLen; |
SndListHandle sndHndl; |
OSErr theErr; |
sndHndl = (SndListHandle)NewHandle(0); |
sndNameHndl = NewString("\p"); |
theErr = MemError(); |
scrapLen = GetScrap((Handle)sndNameHndl, 'STR ', &offset); |
scrapLen = GetScrap((Handle)sndHndl, soundListRsrc, &offset); |
if (scrapLen > 0) { |
if (sndNameHndl != nil) |
PStringCopy(*sndNameHndl, sndName); |
else |
sndName[0] = 0; |
if (StrLength(sndName) == 0) |
GetSndName(sndName); |
theErr = AddSnd(sndDoc, sndName, sndHndl); |
if (theErr == noErr) { |
HPurge((Handle)sndHndl); |
sndHndl = nil; //done with handle |
ActivateSndCntls(sndDoc); |
} |
} else |
theErr = scrapLen; |
if (sndHndl != nil) |
DisposeHandle((Handle)sndHndl); |
if (sndNameHndl != nil) |
DisposeHandle((Handle)sndNameHndl); |
if (theErr != noErr) |
AlertUser(theErr, sEditErr); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is called when an item is chosen from the menu bar (after calling |
MenuSelect or MenuKey). It performs the right operation for each command. |
It is good to have both the result of MenuSelect and MenuKey go to |
one routine like this to keep everything organized. |
VERSION 1.1: Supporting the edit commands for snd resources. |
*/ |
#pragma segment Main |
void DoMenuCommand(long menuResult) |
{ |
WindowPtr window; |
short menuID; |
short menuItem; //resource ID and item of the selected menu |
short daRefNum; |
Str255 daName; |
Boolean ignore; |
menuID = HighWord(menuResult); //use macros for efficiency... |
menuItem = LowWord(menuResult); //to get menu item number and menu number |
switch (menuID) { |
case mApple: |
switch (menuItem) { |
case iAbout: //bring up alert for About |
DoAbout(); |
break; |
default: //all non-About items in this menu are DAs |
GetMenuItemText(GetMenuHandle(mApple), menuItem, daName); |
daRefNum = OpenDeskAcc(daName); |
break; |
} |
break; |
case mFile: |
switch (menuItem) { |
case iNew: |
NewSoundDoc(); |
break; |
case iOpen: |
GetSoundDoc(); |
break; |
case iClose: |
ignore = DoCloseWindow(FrontWindow()); //I donÕt care if cancelled |
break; |
case iQuit: |
Terminate(); |
break; |
} |
break; |
case mEdit: //call SystemEdit for DA editing && MultiFinder |
if (! SystemEdit(menuItem - 1)) { //since I donÕt do any editing |
window = FrontWindow(); |
if (IsDocWindow(window)) { |
switch (menuItem) { |
case iCut: |
CutSnd((SndDocPeek)window); |
break; |
case iCopy: |
CopySnd((SndDocPeek)window); |
break; |
case iPaste: |
PasteSnd((SndDocPeek)window); |
break; |
case iClear: |
ClearSnd((SndDocPeek)window); |
break; |
} |
} |
} |
break; |
case mDemos: |
switch (menuItem) { |
case iCheckVolume: |
CheckSoundVolume(); |
break; |
case iSquareScale: |
PlaySquareSong(rScaleSnd); |
break; |
case iSquareMelody: |
PlaySquareSong(rMelodyPart1); |
break; |
case iSquareTimbre: |
PlaySquareTimbres(); |
break; |
case iWaveScale: |
PlayWaveScale(); |
break; |
case iWaveMelody: |
PlayWaveMelody(); |
break; |
case iWaveSATB: |
PlayWaveSATB(); |
break; |
case iSampleMelody: |
PlaySampleMelody(); |
break; |
case iSampleSATB: |
PlaySampleSATB(); |
break; |
} //switch (menuItem) |
} //switch (menuID) |
HiliteMenu(0); //unhighlight what MenuSelect or MenuKey hilited |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Draw the contents of the application window in response to an update |
event. At this point, BeginUpdate has been called which sets the windowÕs |
visRgn to clip drawing only where it needs to be done. I have the |
controls to draw, a list to update, and a default button to outline. I |
use UpdateControls to avoid needless drawing that happens with DrawControls. |
It not only runs faster but doesnÕt flicker. |
*/ |
#pragma segment Main |
void DrawSndWindow(WindowPtr window) |
{ |
ControlHandle control; |
Rect theRect; |
PenNormal(); |
LUpdate(window->visRgn, ((SndDocPeek)window)->list); // update list |
theRect = (**((SndDocPeek)window)->list).rView; // frame the list |
theRect.right = theRect.right + kScrollbarAdjust; |
InsetRect(&theRect, kListFrameInset, kListFrameInset); |
FrameRect(&theRect); |
UpdateControls(window, window->visRgn); // update controls |
control = (ControlHandle)((WindowPeek)window)->controlList; // draw button outline |
while (control != nil) { |
if (GetControlReference(control) == rPlaySndCntl) |
DoButtonOutline(control); |
control = (**control).nextControl; |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
When the user types a key, I check if it is one that IÕm looking for. |
This routine only handles the non-command key events. Here IÕm looking |
for a arrow key or the return and enter keys for the default button. |
VERSION 1.1: The enter and return keys only work if there is a selection. |
Now supporting the backspace and delete keys which clear the selection. |
*/ |
#pragma segment Main |
void DoKeyDown(char key, WindowPtr window) |
{ |
ControlHandle control; |
Boolean ignore; |
switch (GetWRefCon(window)) { |
case rSoundWindow: |
switch (key) { |
case kEnterKey: |
case kReturnKey: |
if (HasSelection((SndDocPeek)window)) { |
control = (ControlHandle)((WindowPeek)window)->controlList; |
while (control != nil) { //find the default button |
if (GetControlReference(control) == rPlaySndCntl) |
SelectButton(control); //here it is |
control = (**control).nextControl; |
} |
PlaySelectedSnd((SndDocPeek)window, sPlayingMsg); |
} |
break; |
case kUpArrow: |
SelectNextCell(((SndDocPeek)window)->list, false); |
ActivateSndCntls((SndDocPeek)window); |
break; |
case kDownArrow: |
SelectNextCell(((SndDocPeek)window)->list, true); |
ActivateSndCntls((SndDocPeek)window); |
break; |
case kBackspace: |
ClearSnd((SndDocPeek)window); |
break; |
} |
break; //rSoundWindow |
case rStatusWindow: |
if ( (key == kEnterKey) |
|| (key == kReturnKey) |
|| (key == kEscape) ) { |
SelectButton((ControlHandle)((WindowPeek)window)->controlList); |
KillSound(); |
} |
break; |
case rAboutWindow: |
if ( (key == kEnterKey) |
|| (key == kReturnKey) |
|| (key == kEscape) ) { |
SelectButton((ControlHandle)((WindowPeek)window)->controlList); |
ignore = DoCloseWindow(window); |
} |
break; |
} //switch (GetWRefCon(window)) |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Given a document, this will handle any clicking in the list. The mouse |
point (event.where) is expected to have been adjusted to the local |
coordinates of the window. If this routine does handle the event, then |
I return true. |
BUG NOTE: LClick will return a double-click even if no cell was selected. |
So I have to test for the last click being in a real cell. If not, then |
I will not process the double click. Also, there is another bug in the |
List Manager. It will not always deselect the currently selected cell when |
the user clicks outside of the data bounds. In other words, sometimes |
my list would show 4 items when the list has room to show 8. If the user |
clicked in the bottom area of the list (below the last item) the List |
Manager should deselect any items. It doesnÕt all the time, just sometimes. |
The only real solution would be to write a new LClick. |
*/ |
#pragma segment Main |
Boolean ListClick(SndDocPeek sndDoc, EventRecord *event) |
{ |
Cell aCell; |
Rect listRect; |
Boolean result; |
listRect = (**(sndDoc->list)).rView; |
listRect.right = listRect.right + kScrollbarAdjust; |
if (PtInRect(event->where, &listRect)) { |
if (LClick(event->where, event->modifiers, sndDoc->list)) { |
aCell = LLastClick(sndDoc->list); |
if ( PtInRect(aCell, &((**(sndDoc->list)).dataBounds)) ) |
PlaySelectedSnd(sndDoc, sPlayingMsg); |
} |
ActivateSndCntls(sndDoc); |
result = true; //I handled the event |
} else |
result = false; |
return(result); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is called when a mouse-down event occurs in the content of my |
application windows. First thing to check for is a click in the list. |
If not a list click, then find what control may have been clicked in and |
handle that. |
VERSION 1.1: Handle the record button. |
*/ |
#pragma segment Main |
void DoSndDocClick(SndDocPeek sndDoc, EventRecord *event) |
{ |
ControlHandle control; |
SetPort((GrafPtr)sndDoc); |
GlobalToLocal(&event->where); |
if (! ListClick(sndDoc, event)) { |
if (FindControl(event->where, (WindowPtr)sndDoc, &control) != 0) { |
if (TrackControl(control, event->where, nil) != 0) { |
switch (GetControlReference(control)) { |
case rPlaySndCntl: |
PlaySelectedSnd(sndDoc, sPlayingMsg); |
break; |
case rHyperPlayCntl: |
PlaySelectedSnd(sndDoc, sHyperMsg); |
break; |
case rPlayScaleCntl: |
PlaySndSong(sndDoc, rScaleSnd); |
break; |
case rMelodyCntl: |
PlaySndSong(sndDoc, rMelodyPart1); |
break; |
case rStopCntl: |
KillSound(); |
break; |
case rRecordCntl: |
DoRecordSound(sndDoc); |
break; |
} //switch GetControlReference(control) |
} //if TrackControl |
} //if FindControl |
} //if ! ListSelect |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
The status window has a stop button. This will stop any sound in |
progress. So, if the user clicks in my status window I need to check |
for this. |
*/ |
#pragma segment Main |
void DoStatClick(StatWindowPeek statWindow, EventRecord *event) |
{ |
ControlHandle control; |
SetPort((GrafPtr)statWindow); |
GlobalToLocal(&(event->where)); |
if (FindControl(event->where, (WindowPtr)statWindow, &control) != 0) |
if (TrackControl(control, event->where, nil) != 0) |
KillSound(); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
The about window has a single OK button. If the users clicks in it, then |
close the window. |
*/ |
#pragma segment Main |
void DoAboutClick(WindowPtr window, EventRecord *event) |
{ |
ControlHandle control; |
Boolean ignore; |
SetPort(window); |
GlobalToLocal(&event->where); |
if (FindControl(event->where, (WindowPtr)window, &control) != 0) { |
if (TrackControl(control, event->where, nil) != 0) |
ignore = DoCloseWindow(window); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is called when an update event is received for any of my windows. It |
calls the appropriate windowÕs update routine to draw its contents. As an |
efficiency measure that does not have to be followed, it calls the drawing |
routine only if the visRgn is non-empty. This will handle situations |
where calculations for drawing or drawing itself is very time-consuming. |
Why does QD give you an update with an empty updateRgn? |
*/ |
#pragma segment Main |
void DoUpdate(WindowPtr window) |
{ |
BeginUpdate(window); //setup the visRgn, clears updateRgn |
if (! EmptyRgn(window->visRgn)) { //if updating to be done |
SetPort(window); //set to the current port |
switch (GetWRefCon(window)) { //call the windowÕs drawing routine |
case rSoundWindow: |
DrawSndWindow(window); |
break; |
case rStatusWindow: |
DrawStatusWindow(); |
break; |
case rAboutWindow: |
DrawAboutWindow(window); |
break; |
} |
} |
EndUpdate(window); //restores the visRgn |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This is called when a window is to be activated or deactivated. For the |
document window I activate all the controls and the list. For the status |
window I activate the one and only control. This is also called for |
suspend and resume events while running MultiFinder. |
*/ |
#pragma segment Main |
void DoActivate(WindowPtr window, Boolean becomingActive) |
{ |
if (window != nil) { |
switch (GetWRefCon(window)) { |
case rSoundWindow: |
ActivateSndCntls((SndDocPeek)window); |
LActivate(becomingActive, ((SndDocPeek)window)->list); |
break; |
case rStatusWindow: |
if (becomingActive) //it only has one control |
HiliteControl((ControlHandle)((WindowPeek)window)->controlList, kControlNoPart); |
else |
HiliteControl((ControlHandle)((WindowPeek)window)->controlList, kControlInactivePart); |
DoButtonOutline((ControlHandle)((WindowPeek)window)->controlList); |
break; |
case rAboutWindow: |
if (becomingActive) //it only has one control |
HiliteControl((ControlHandle)((WindowPeek)window)->controlList, kControlNoPart); |
else |
HiliteControl((ControlHandle)((WindowPeek)window)->controlList, kControlInactivePart); |
DoButtonOutline((ControlHandle)((WindowPeek)window)->controlList); |
break; |
} |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
The required AppleEvent sent to tell an application to quit. Nothing much to do. |
Just call our terminate routine to clean up and then exit through the event loop. |
Note that you cannot exit from here, the system will crash. You must return to |
the AppleEvent Manager. Do not call ExitToShell at this point! |
*/ |
#pragma segment Main |
pascal OSErr QuitApplicationEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) |
{ |
#pragma unused (theAppleEvent, reply, handlerRefcon) |
Terminate(); |
return(noErr); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
One of the required AppleEvents. This is a request to open a document. Find |
the document being requested, and open it. |
*/ |
#pragma segment Main |
pascal OSErr OpenDocumentsEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) |
{ |
#pragma unused (reply, handlerRefcon) |
FSSpec file; |
AEDescList docList; |
long i; |
long itemsInList; |
Size actualSize; |
AEKeyword keyword; |
DescType returnedType; |
OSErr theErr; |
// get the direct parameter--a descriptor list--and put it into docList |
theErr = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList, &docList); |
if (theErr == noErr) |
{ |
if (docList.descriptorType != typeAEList) |
{ |
AEDisposeDesc (&docList); |
theErr = paramErr; |
} |
else |
{ |
// count the number of descriptor records in the list |
theErr = AECountItems(&docList, &itemsInList); |
if (theErr == noErr) |
{ |
//get each descriptor record from the list, coerce the returned data |
//to an FSSpec and open the associated file |
for (i = 1; i <= itemsInList; i++) |
{ |
theErr = AEGetNthPtr(&docList, i, typeFSS, &keyword, &returnedType, |
&file, sizeof(file), &actualSize); |
if (theErr == noErr) |
OpenSoundDoc(&file); |
} |
} |
AEDisposeDesc (&docList); |
} |
} |
return (theErr); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
It's a required AppleEvent and we don't use it. |
*/ |
#pragma segment Main |
pascal OSErr PrintDocumentsEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) |
{ |
#pragma unused (theAppleEvent, reply, handlerRefcon) |
return (errAEEventNotHandled); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
It's a required AppleEvent and we don't use it. |
*/ |
#pragma segment Main |
pascal OSErr OpenApplicationEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) |
{ |
#pragma unused (theAppleEvent, reply, handlerRefcon) |
return (noErr); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
As Spike Lee says, ÒDo the right thing.Ó Determine what kind of event it |
is, and call the appropriate routines. Key down events are first tested |
as being command key events for menus or command-. for cancel. If itÕs |
the command-. the user is asking to cancel the sound. In this case, if |
the front window is the status window then the button should look as if it |
has been clicked. This is proper human interface. Any non-menu command |
keys are passed to DoKeyDown. I have a global flag, gInModalState, which |
is used to handle the front window as a modal dialog. This means I ignore |
clicks outside of the modal window and menu command keys. I could have a |
different global flag that would mean itÕs a modal window but uses menu |
commands, such as gInModalMenuState. An important note is that I do pass |
any other non-menu command keys to DoKeys, so the modal window could have |
access to key events. One last thing to note happens during a suspend |
event. Applications using sound should dispose of their sound channels |
at suspend time. No other application can use sound while another one has |
channels allocated. To stop my sound, I call KillSound which hides the |
status window. MultiFinder does properly deactivate the front window at |
suspend time and applications normally do not have to worry about |
HiliteWindow. I do because the front window could have been the status |
window and IÕve removed it after getting a suspend event. This causes the |
next window, a document window, to become highlighted as the active |
window. To avoid my next window from being highlighted while in the |
background, I have to call HiliteWindow *after* KillSound. This is |
strange and only applications changing the front window at suspend event |
time have to be concerned about this. |
VERSION 1.1: Added a constant, kSFTopLeft, to specify the dialog position. |
I do not call KillSound during a MultiFinder switch unless we're running |
the older Sound Manager. The new one allows for multiple sound |
channels or will return the proper error otherwise. |
*/ |
#pragma segment Main |
void DoEvent(EventRecord *event) |
{ |
WindowPtr window; |
Point where; |
short part; |
short err; |
Boolean ignore; |
char key; |
switch (event->what) { |
case mouseDown: |
part = FindWindow(event->where, &window); |
if ( (IsModalWindow(FrontWindow()) && (window != FrontWindow())) |
|| (IsModalWindow(FrontWindow()) && (part == inMenuBar))) |
{ |
SysBeep(30); //click outside of modal window |
return; //break out of routine |
} |
switch (part) { |
case inMenuBar: //process the menu command |
AdjustMenus(); |
DoMenuCommand(MenuSelect(event->where)); |
break; |
case inSysWindow: //let system handle the mouseDown |
SystemClick(event, window); |
break; |
case inContent: |
if ((window != FrontWindow())) |
SelectWindow(window); |
else { |
switch (GetWRefCon(window)) { |
case rSoundWindow: |
DoSndDocClick((SndDocPeek)window, event); |
break; |
case rStatusWindow: |
DoStatClick((StatWindowPeek)window, event); |
break; |
case rAboutWindow: |
DoAboutClick(window, event); |
break; |
} //switch (GetWRefCon(window)) |
} |
break; //inContent |
case inDrag: //pass screenBits.bounds to get all gDevices |
DragWindow(window, event->where, &qd.screenBits.bounds); |
break; |
case inGoAway: |
if (TrackGoAway(window, event->where)) |
ignore = DoCloseWindow(window); |
break; |
} //switch (part) |
break; //mouseDown |
case keyDown: |
case autoKey: |
window = FrontWindow(); |
key = event->message & charCodeMask; |
if ((event->modifiers & cmdKey) != 0) //Command key down? |
{ |
if (key == kPeriod) |
{ |
if (window == (WindowPtr)gStatusWindow) |
SelectButton((ControlHandle)((WindowPeek)gStatusWindow)->controlList); |
KillSound(); |
} else |
if ((event->what == keyDown) && (! IsModalWindow(window))) |
{ |
AdjustMenus(); //adjust items properly |
DoMenuCommand(MenuKey(key)); |
} |
} else //non-Command keys |
if (window != nil) //if thereÕs a window |
DoKeyDown(key, window); |
break; |
case activateEvt: //true for activate, false for deactivate |
DoActivate((WindowPtr)event->message, event->modifiers & activeFlag); |
break; |
case updateEvt: //call DoUpdate with the window to update |
DoUpdate((WindowPtr)event->message); |
break; |
case diskEvt: //Call DIBadMount in response to a diskEvt |
if (HighWord(event->message) != noErr) { |
where.v = kRecordTop; |
where.h = kRecordLeft; |
err = DIBadMount(where, event->message); |
} |
break; |
case osEvt: |
switch (event->message >> 24) { //get high byte of message |
case suspendResumeMessage: |
if ((event->message & suspendResumeMessage) != 0) |
gInBackground = false; //it was a resume event |
else { |
gInBackground = true; //it was a suspend event |
if (GetSoundMgrVersion() == 1) |
KillSound(); //stop any sound |
window = FrontWindow(); //get front window |
if (window != nil) //donÕt use a nil window |
HiliteWindow(window, false); //then properly activate it |
} |
DoActivate(FrontWindow(), ! gInBackground); |
break; //suspendResumeMessage |
} |
break; //osEvt |
case kHighLevelEvent: |
AEProcessAppleEvent(event); |
break; |
} //switch (event->what) |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Change the cursorÕs shape, depending on its current position. This also |
calculates the region where the current cursor resides (for |
WaitNextEvent). This is based on its current position in global |
coordinates. If the mouse is ever outside of that region, an event is |
generated causing this routine to be called again by the event loop. This |
allows me to change the region to where the mouse is currently located. |
If there is more to the event than just Òthe mouse moved,Ó this gets |
called before the event is processed to make sure the cursor is the right |
one. In any (ahem) event, this is called again before I fall back into |
WaitNextEvent. Extreme short values are used to create a wide open |
region. -SHRT_MAX - 1 is the largest negative short (-32768) and SHRT_MAX |
is the largest positive short (32767). |
BUG NOTE: The largest positive value for a regionÕs size is SHRT_MAX - 1 |
due to a very old bug that still remains to this day. |
*/ |
#pragma segment Main |
void AdjustCursor(RgnHandle region) |
{ |
WindowPtr window; |
RgnHandle arrowRgn; |
RgnHandle sndCursorRgn; |
Rect sndCursorRect; |
window = FrontWindow(); //I only adjust the cursor when I am in front |
if ((! gInBackground) && (! IsDAWindow(window))) { |
arrowRgn = NewRgn(); //calculate regions for different cursor shapes |
sndCursorRgn = NewRgn(); //start with a big, big rectangular region |
SetRectRgn(arrowRgn, -SHRT_MAX - 1, -SHRT_MAX - 1, SHRT_MAX - 1, SHRT_MAX - 1); |
if (IsDocWindow(window)) { //calculate region for document cursor |
sndCursorRect = window->portRect; |
SetPort(window); |
//make a global version of the viewRect |
LocalToGlobal(&TopLeft(sndCursorRect)); |
LocalToGlobal(&BottomRight(sndCursorRect)); |
RectRgn(sndCursorRgn, &sndCursorRect); |
SetOrigin(-window->portBits.bounds.left, -window->portBits.bounds.top); |
SectRgn(sndCursorRgn, window->visRgn, sndCursorRgn); |
SetOrigin(0, 0); |
} |
DiffRgn(arrowRgn, sndCursorRgn, arrowRgn); //subtract other region |
if (PtInRgn(GetGlobalMouse(), sndCursorRgn)) { |
SetCursor(*(GetCursor(rSndCursor))); //change cursor and region |
CopyRgn(sndCursorRgn, region); |
} else { |
SetCursor(&qd.arrow); |
CopyRgn(arrowRgn, region); |
} |
DisposeRgn(arrowRgn); //get rid of our local regions |
DisposeRgn(sndCursorRgn); |
} |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Get events forever, and handle them by calling DoEvent. Since this |
application requires System 6.0x or later I know that _WaitNextEvent is |
always there, even without MultiFinder. MultiFinderÕs sleep is used to |
determine how often I want to receive events, regardless if an event has |
actually occurred. In this application, I donÕt perform any background |
processing so IÕm being super friendly by using LONG_MAX as a sleep |
value. This also helps keep Virtual Memory from paging me in just to run |
my event loop and find out that nothing happened. The application will |
only be called upon for events that must be handled. Also call |
AdjustCursor each time through the loop. AdjustCursor will return the |
current region containing the mouse and is passed to WaitNextEvent. If |
the mouse travels out of the region, another event is generated. I have |
to call AdjustCursor just before doing the event to make sure the right |
cursor is shown. Another thing, if I have a sound playing asynchronously |
then I want to dispose of my channel as soon as possible. I set up a flag |
in the SoundUnit that will keep track of when it has a channel allocated. |
I can call the HasChannelOpen() function to find out if this is true. It |
pretty much like keeping track of when youÕre in the background. If a |
channel is open, then the MultiFinder sleep time is adjusted to a |
reasonable time that will allow me to catch when the sound has completed |
so that I may dispose of my channels and status window. ThatÕs when I |
return kPollingSleepTime. |
VERSION 1.1: No longer putting the SANELib into a seperate segment, which then |
needed to be unloaded. Instead, I merge it into the Main segment. Refer |
to the Make file for further information. |
VERSION 1.2: No longer using the SANELib at all. All of the SANE calls |
are inline, and do not need a library to be linked in. |
*/ |
#pragma segment Main |
void EventLoop(void) |
{ |
RgnHandle cursorRgn; |
EventRecord event; |
long sleep; |
cursorRgn = NewRgn(); //1st time pass WNE an empty region |
while (!gTerminate) { |
if (HasChannelOpen()) //if weÕre playing a sound |
sleep = kPollingSleepTime; //use the polling sleep value |
else { |
sleep = LONG_MAX; //default value for sleep |
UnloadSeg(_SoundUnit); //unload the Sound Unit |
} |
UnloadSeg(OpenSoundDoc); //unload the open code |
if (HasSoundCompleted()) |
KillSound(); |
if (LowOnReserve()) |
RecoverReserve(); |
AdjustCursor(cursorRgn); //get the right cursor |
if (WaitNextEvent(everyEvent, &event, sleep, cursorRgn)) { |
AdjustCursor(cursorRgn); //get the right cursor |
DoEvent(&event); |
} |
}; //loop forever; I quit through Terminate |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
Set up the whole world. Initialize global variables, Toolbox managers, |
and menus. If a failure occurs here, I will consider that the application |
is in such bad shape that I should just exit. Your error handling may |
differ, but the checks should still be made. I ask for a set of master |
pointer blocks and all permanent storage at this point to cut down on |
memory fragmentation. I may be opening large numbers of resources and |
documents so I allocate some extra master pointer blocks at the start. |
VERSION 1.1: Now supports opening of documents from the Finder. |
*/ |
#pragma segment Initialize |
void Initialize(void) |
{ |
#define kBroughtToFront 3 |
EventRecord event; |
Handle menuBar; |
long response; |
short count; |
OSErr ignoreErr; |
Boolean ignoreResult; |
gTerminate = false; |
gInBackground = false; //weÕll be in the foreground soon |
gAppResRef = CurResFile(); //save the resRef to myself |
for (count = 1; count <= kNumberOfMasters; count++) //allocate master pointer blocks |
MoreMasters(); |
InitGraf(&qd.thePort); //init managers, yawn... |
InitFonts(); |
InitWindows(); |
InitMenus(); |
TEInit(); |
InitDialogs(nil); |
InitCursor(); |
/* |
ErrorSound is used to prevent the Dialog Manager from calling _SysBeep. |
If I get a memory or resource Manager error it wouldnÕt be the best |
of plans to call _SysBeep which will want to allocate memory and load |
a few resources. Which could be bad if I just gave an Alert signalling |
an out of memory condition. You only have to install this for Sound Manager |
version 1 which is pretty buggy. |
VERSION 1.2: Since we're using Sound Manager 2 or later, we don't have to |
disable the multiple sampled sound channels demo and we don't need DoErrorSound. |
if (GetSoundMgrVersion() == 1) |
{ |
ErrorSound(GetRoutineAddress(DoErrorSound)); |
DisableItem(GetMenu(mDemos), iSampleSATB); |
DisableItem(GetMenu(mDemos), iSampleMelody); |
} |
*/ |
/* |
This code is necessary to pull the application into the foreground. I use |
EventAvail because I donÕt want to remove any events the user may have |
done, such as typing ahead. Until the application has made a few calls (3 |
seems to be the magic number) to the Event Manager, MultiFinder keeps me |
in the background. Splashscreens and Alerts will remain in a background |
layer until we get a few events. This is documented in Tech Note #180. |
*/ |
for (count = 1; count <= kBroughtToFront; count++) |
ignoreResult = EventAvail(everyEvent, &event); |
/* |
Ignore the error returned from SysEnvirons; even if an error occurred, |
the SysEnvirons glue will fill in the SysEnvRec. You can save a redundant |
call to SysEnvirons by calling it after initializing AppleTalk. |
VERSION 1.2: Using Gestalt and checking for System 7. Make sure you know if |
the Gestalt trap is available before calling it. This code is compiled with the |
SystemSevenOrLater flag on, when means you do not get the safe glue for Gestalt. |
*/ |
if (! TrapExists(_Gestalt)) |
EmergencyExit(sWrongVersion); |
if (Gestalt(gestaltSystemVersion, &response) != noErr) |
EmergencyExit(sStandardErr); |
if (response < 0x0700) |
EmergencyExit(sWrongVersion); |
/* |
Call the SoundUnit to initialize itslef. If the SoundUnit encounters an error, |
then it cannot be used and this means IÕm leaving too. |
*/ |
if (InitSoundUnit() != noErr) //allocates 4 * 1064 bytes |
EmergencyExit(sInitSoundErr); |
/* |
Before I go any further, I want my reserve memory. This is an emergency |
reserve (sorta like my old VW had) when memory runs low. If I cannot |
obtain this reserve, then IÕll bail. ItÕs also important to obtain my |
reserve before testing if I have the desired amount of memory to run |
this application. Also, FailLowMemory will consider the memory reserve. |
*/ |
if (! AllocateReserve()) |
EmergencyExit(sLowMemory); |
SetGrowZone(GetRoutineAddress(MyGrowZone)); |
menuBar = GetNewMBar(rMenuBar); //read menus into menu bar |
if (menuBar == nil) |
EmergencyExit(sNoMenus); //wow, howÕd that happen? |
SetMenuBar(menuBar); //install menus |
DisposeHandle(menuBar); |
AppendResMenu(GetMenuHandle(mApple), 'DRVR'); //add DA names to Apple menu |
DrawMenuBar(); |
InitStatusWindow(); //get the status window ready |
//InitCursorCtl(nil); //MPWÕs handy cursor routines |
/* |
Last, I want to make sure that enough memory is free for my application to |
run. It is possible that user may have adjusted the SIZE resource to too |
small a setting or for some other reason the application started up in a |
very small memory partition. ItÕs also possible for a situation to arise |
where the heap may have been of the requested size taken from the SIZE |
resource, but a large scrap was loaded which left too little memory. I |
want to make sure that my free memory is not being modified by the scrapÕs |
presence. So, I unload it to disk but if the application will run once |
the scrap is unloaded, then youÕll probably not get it back into memory. |
Thus losing the clipboard contents. I preform this check after |
initializing all the Toolbox and the basic features of this application, |
such as showing the about box. |
*/ |
if (FailLowMemory(kMinSpace)) { |
if (UnloadScrap() != noErr) |
EmergencyExit(sLowMemory); |
else { |
if (FailLowMemory(kMinSpace)) |
EmergencyExit(sLowMemory); |
} |
} |
/* |
Install the four core AppleEvent handlers. |
*/ |
ignoreErr = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, |
GetRoutineAddress(QuitApplicationEvent), 0, false); |
ignoreErr = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, |
GetRoutineAddress(OpenDocumentsEvent), 0, false); |
ignoreErr = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, |
GetRoutineAddress(PrintDocumentsEvent), 0, false); |
ignoreErr = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, |
GetRoutineAddress(OpenApplicationEvent), 0, false); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
This routine is contained in the MPW runtime library. It will be placed |
into the code segment used to initialize the A5 globals. This external |
reference to it is done so that we can unload that segment, named %A5Init. |
*/ |
#ifdef applec |
extern void _DataInit(void); |
#endif |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN PROGRAM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
/* |
If you have stack requirements that differ from the default, then you |
could use SetApplLimit to increase StackSpace at this point, before |
calling MaxApplZone. |
*/ |
#pragma segment Main |
void main(void) |
{ |
#ifdef applec |
UnloadSeg(_DataInit); //note that _DataInit must not be in Main! |
#endif |
MaxApplZone(); //expand the heap so code segments load at the top |
Initialize(); //initialize the program |
UnloadSeg(Initialize); //note that Initialize must not be in Main! |
EventLoop(); //call the main event loop |
ExitToShell(); //we're out of here! |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN PROGRAM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-03-14