Jump to content

Playing continuous sound on a MacPlus


Recommended Posts

Hi,

 

For a current project (mmm, what could that be?), I need to play continous digital sound on a MacPlus, with an old system (6.0.8 right now).

 

I tried the SoundManager routines, with little success, so I ended up with the real vintage StartSound(), below.

 

C Code, "works" on THINK C 5, should loop sound until mouse clicked.

 

However, it only plays 2 samples and stops sound.

 

It is driving me crazy.

 

Any idea?

 

#include <Sound.h>

#define SAMPLES (370*50L)

FFSynthPtr snd;

pascal void MyStartSound()
{
	asm
	{
		move.l a5, -(a7)
		move.l 0x904, a5
	}

	StartSound( snd, SAMPLES, (ProcPtr)MyStartSound );

	asm
	{
		move.l (a7)+,a5
	}
}

int main()
{
	long i;
	int n = 0;
	int k = 1;
	
	snd = (FFSynthPtr)NewPtr( 2+4+SAMPLES );
	snd->mode = ffMode;
	snd->count = FixRatio( 1,1 );
	for (i=0;i!=SAMPLES;i++)
	{
		snd->waveBytes[i] = n<k/2?0:255;
		n = n+1;
		if (n==k)
		{
			n = 0;
			k++;
		}
	}

	MyStartSound();

	while (!Button())
		;

	DisposePtr( snd );
	return 0;
}

 

Link to post
Share on other sites
  • 68kMLA Supporter

I haven’t tried to do this with the old Sound Driver since probably 1990, but a couple questions ...

 

I assume you aren’t running 6.0.8 under MultiFinder?  If you are, the completion routine could get called when your application has switched to the back, and CurrentA5 (value at 0x904) would point to someone else’s globals, and you’ll crash or play garbage the second time.

 

You are calling StartSound at interrupt time.  Inside Mac lists StartSound as possibly moving or purging memory, are you sure this is OK to do?  (I know it also says this might be a way to play continuous sound if you don’t use the Memory Manager in your completion routine ... then again, it also says to use the Sound Manager instead :) ).  I would probably increment a global counter in the completion routine and poll for that in main(), then just call StartSound again.

 

Are you saying it literally plays two samples, or it plays the whole sound twice then stops?  Your code looks fine otherwise ....

 

 

Link to post
Share on other sites

No, I am not running under MultiFinder. I looked at other ideas to get A5, but found none.

 

You are calling StartSound at interrupt time.  Inside Mac lists StartSound as possibly moving or purging memory, are you sure this is OK to do?

 

Well, this is what Inside Mac vol II, page 231 says on StartSound:

 

"You may want the completion routine to start the next sound when one sound
finishes, but beware: Completion routines are executed at the interrupt level and must
preserve all registers other than AO, A l , and D0-D2."

 

Adding that the May1985 software supplements says "calling StartSound asynchronously works better than it did previously.", so I suspected that it is supposed to somewha work.

 

I orginally tried to use the bufferCmd, but it had audible holes, and inside Mac says: "Using the bufferCmd command to play several consecutive compressed samples on the Macintosh Plus, the Macintosh SE, or the Macintosh Classic is not guaranteed to work without an audible pause or click.". I looked at the doc for SndPlayDoubleBuffer, but found somewhere that is wasn't ok for the Plus either.

 

I also recovered a fragment of my old source code of the Captain Blood port, and StartSound+callback was was the way I played continuous sound in the intro.

 

I would probably increment a global counter in the completion routine and poll for that in main(), then just call StartSound again.

 

 

My main code is already *seriously* busy, I don't see that working.

 

Are you saying it literally plays two samples, or it plays the whole sound twice then stops?  Your code looks fine otherwise ....

 

It plays the whole sound twice, with no click, and stops.

 

Taking *any* advice :-)

 

Link to post
Share on other sites
  • 68kMLA Supporter
Posted (edited)

Yeah, I read that too last night.  It’s self-contradictory though because StartSound is definitely listed as a routine that shouldn’t be called at interrupt time ...

 

Are you sure that the compiler-generated code for your completion routine, including the StartSound glue (that’s not in ROM, remember) is preserving everything except A0, A1, and D0-D2?  My only guess would be that maybe it’s not.  Check out the disassembly, or rewrite your completion routine entirely in asm{}, or just push the other protected registers onto the stack like you do with A5 and pop them off later.

 

23 hours ago, fstark said:

No, I am not running under MultiFinder. I looked at other ideas to get A5, but found none.

 

See IM II-236:  StartSound is glue routine that calls the Device Manager’s _Write trap.  See IM II-182, inside a Device Manager completion routine, A0 contains a pointer to the parameter block passed to _Write.  So:  declare your own struct with one extra field at the end (offset 50 I think, but check), stick a pointer to your private storage in that extra field at offset 50, and pass a pointer to that one-longword-longer struct to Write.  Then inside the completion routine, get the thing at A0+50 and dereference that.  Now you have access to your private storage in the completion routine.

 

This approach also avoids calling the StartSound glue, which the Tech Notes say had bugs even in its final version.  If you do it in assembly, it would also make explicit which registers you’re using to ensure you aren’t breaking any rules at interrupt time.  Finally, calling _Write yourself means you can look at D0 afterward to see if there was an error (_Write returns its OSErr in D0).

 

Best I could come up with anyway!  Good luck.

 

 

Edited by Crutch
Link to post
Share on other sites
  • 68kMLA Supporter
23 hours ago, fstark said:

calling StartSound asynchronously works better than it did previously

 

No advice, I'm afraid, other than sympathy; this is exactly the comment one wants not to find in documentation!

Link to post
Share on other sites
  • 68kMLA Supporter
Posted (edited)

I just tried your code on my SE/30 under 7.5.5 and got the same result.  The app dies after the second sound (which plays cleanly) -- something was going wrong in the completion routine that gets called after it's installed the second time (i.e., the first time it's installed at interrupt time).

 

I replaced StartSound with a direct call to PBWrite using a ParamBlockRec that I allocate as a global on the stack (so I don't, of course, call NewPtr at interrupt time, and also so the data will still be there for the Device Manager to see asynchronously later on) and it works fine -- the sound plays forever.  Here's my code.

 

I figured this would work when I noticed IM lists StartSound as illegal to call interrupt time, but not PBWrite.  Something in the StartSound glue is, apparently, moving memory for some reason.  Perhaps it's dynamically allocating a ParamBlock?

 

Note that your DisposePtr() call at the end may crash or result in you hearing random noise since the sound is still playing at that point.  You need to stop the sound playing, but StopSound (or KillIO) calls the completion routine itself, so you also need to set a global to tell the completion routine not to play again!  I did that in my code and it works.

 

 

#include <Sound.h>

#define SAMPLES (370*50L)

FFSynthPtr snd;
ParamBlockRec pb;
Boolean stopSound = false;

pascal void MyStartSound()
{
	OSErr err;
	
	asm
	{
		move.l a5, -(a7)
		move.l 0x904, a5
	}

	if (!stopSound)
	{
		pb.ioParam.ioRefNum = -4;
		pb.ioParam.ioBuffer = (Ptr) snd;
		pb.ioParam.ioReqCount = SAMPLES;
		pb.ioParam.ioCompletion = (ProcPtr) MyStartSound;
		
		err = PBWrite(&pb, TRUE);
	}
	
	asm
	{
		move.l (a7)+, a5
	}
}


main()
{
	long i;
	int n = 0;
	int k = 1;
	
	snd = (FFSynthPtr) NewPtr(2+4+SAMPLES);
	snd->mode = ffMode;
	snd->count = FixRatio(1,1);
	
	for (i = 0; i != SAMPLES; i++)
	{
		snd->waveBytes[i] = n<k/2 ? 0 : 255;
		n++;
		
		if (n == k)
		{
			n = 0;
			k++;
		}
	}
	
	MyStartSound();
	
	while (!Button())
		;
	
	stopSound = true;
	KillIO(-4);  /* stops all pending StartSounds, see Sound Manager in IM2 */
	
	DisposePtr((Ptr) snd);

	return 0;
}

 

Edited by Crutch
Link to post
Share on other sites
  • 68kMLA Supporter
Posted (edited)

Bah, this worked earlier but now appears to be unreliable ... I'm leaving it above in case helpful but I must have done something wrong, sometimes it doesn't play anything (doesn't crash though...).  Will continue to investigate this evening ...

 

Edit:  correction, actually I think the above is all good as written, THINK C was just acting weird for a minute.  Let me know if this works for you @fstark!

 

p.s.  I confirmed in the Disassembly window that all protected registers were preserved by THINK C's compilation of the completion routine above.

Edited by Crutch
Link to post
Share on other sites

This is awesome!

 

I was going to try the _Write method (as InsideMac says: "StartSound Call Write with ioRefNum=-4, ioBuffer=synthRec, ioReqCount=numBytes" ), but you beat me to it.

 

Going to check this right now and will report the results. I owe you a lot.

Link to post
Share on other sites

 

On 4/6/2021 at 5:34 PM, cheesestraws said:

> calling StartSound asynchronously works better than it did previously

 

No advice, I'm afraid, other than sympathy; this is exactly the comment one wants not to find in documentation!

 

I had a few moments like that in the last few days. Tried to understand the arguments of some sound format, and the only thinkg InsideMac said was that it was an "extended80". To understand, I went into THINK C headers, to find this gem: 

 

    typedef struct { short man[4]; } comp;
    typedef struct { short exp[1], man[4]; } extended80;
    typedef struct { short exp[2], man[4]; } extended96;
    typedef extended80 __extended;    //  <-- this line is magic
    typedef __extended extended;
 

I really remembered why I dropped developping on Macs as soon as I started developing on NeXT in 1990...

 

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...