1 /*
2 
3 Copyright (C) 2015-2018 Night Dive Studios, LLC.
4 
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 */
19 //	==============================================================
20 //		Convert a raw QT movie (from the LG .MOV file) into a nice compressed movie.
21 //		Make a separate movie for each palette change.
22 //	==============================================================
23 
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "2d.h"
28 #include "fix.h"
29 #include "lg.h"
30 #include "quiktime.h"
31 #include "rect.h"
32 
33 #include "InitMac.h"
34 #include "ShockBitmap.h"
35 extern Ptr gScreenAddress;
36 extern long gScreenRowbytes;
37 
38 #include <ImageCompression.h>
39 #include <Movies.h>
40 #include <QuickTimeComponents.h>
41 #include <Sound.h>
42 
43 typedef struct {
44   long frameNum;
45   short palID;
46 } PalChange;
47 
48 //--------------------------
49 //  Globals the user should set
50 //--------------------------
51 char gInputMov[] = "INTRO.QTM";
52 FSSpec gInputPal = {0, 0, "Intro Palettes"};
53 FSSpec gInputSnd = {0, 0, "INTRO"};
54 
55 #define codec 'smc '
56 #define spatialQ codecHighQuality
57 #define temporalQ codecHighQuality
58 #define codecType bestCompressionCodec
59 #define kPrevious codecFlagUpdatePreviousComp
60 
61 //  Globals
62 WindowPtr gMainWindow;
63 extern short gMainVRef;
64 short gResNum;
65 ulong *gSampleTimes;
66 ulong *gChunkOffsets;
67 long gNumFrames;
68 
69 ImageDescription **gImageDescriptionH = 0L; // Contains info about the sample
70 Movie gMovie = 0;                           // Our movie, track and media
71 Track gTrack;
72 Media gMedia;
73 short gMovieResNum;
74 Rect gMovieRect;
75 Handle gCompressedFrameBitsH = 0L; // Buffer for the compressed data
76 ImageSequence gSeq;
77 
78 QT_ChunkInfo chunkInfo[] = {QT_CLIP, FALSE, QT_CRGN, TRUE,  QT_DINF, FALSE, QT_DREF, TRUE,  QT_EDTS, FALSE,
79                             QT_ELST, TRUE,  QT_HDLR, TRUE,  QT_KMAT, TRUE,  QT_MATT, FALSE, QT_MDAT, TRUE,
80                             QT_MDIA, FALSE, QT_MDHD, TRUE,  QT_MINF, FALSE, QT_MOOV, FALSE, QT_MVHD, TRUE,
81                             QT_SMHD, TRUE,  QT_STBL, FALSE, QT_STCO, TRUE,  QT_STSC, TRUE,  QT_STSD, TRUE,
82                             QT_STSH, TRUE,  QT_STSS, TRUE,  QT_STSZ, TRUE,  QT_STTS, TRUE,  QT_TKHD, TRUE,
83                             QT_TRAK, FALSE, QT_UDTA, FALSE, QT_VMHD, TRUE,  0,       0};
84 TrackType currTrackType;
85 
86 //  Prototypes
87 void main(void);
88 void CheckError(OSErr error, Str255 displayString);
89 void SetInputSpecs(void);
90 void SetPalette(short palID);
91 void CreateAMovie(void);
92 void EndAMovie(void);
93 
94 uchar QuikReadChunkHdr(FILE *fp, QT_ChunkHdr *phdr);
95 QT_ChunkInfo *QuikFindChunkInfo(QT_ChunkHdr *phdr);
96 void QuikSkipChunk(FILE *fp, QT_ChunkHdr *phdr);
97 
98 void CreateMySoundTrack(Movie theMovie);
99 void CreateSoundDescription(Handle sndHandle, SoundDescriptionHandle sndDesc, long *sndDataOffset, long *numSamples,
100                             long *sndDataSize);
101 long GetSndHdrOffset(Handle sndHandle);
102 
103 void MyCreateTextTrack(Movie theMovie);
104 
105 //	---------------------------------------------------------------
106 //		MAIN PROGRAM
107 //	---------------------------------------------------------------
108 
main(void)109 void main(void) {
110   grs_screen *screen;
111   Ptr p;
112   long stupid;
113   OSErr result;
114 
115   FILE *fp;
116   uchar *dbuff;
117   ulong dbuffLen;
118   QT_ChunkHdr chunkHdr;
119   QT_ChunkInfo *pinfo;
120 
121   short palResNum, sndResNum;
122   Handle palChgHdl;
123   PalChange *pcp;
124 
125   Rect r;
126   long compressedFrameSize; /* Size of current compressed frame */
127 
128   //---------------------
129   //	Init graphics system
130   //---------------------
131   InitMac();
132   CheckConfig();
133 
134   SetupWindows(&gMainWindow); // setup everything
135   SetupOffscreenBitmaps();
136 
137   gr_init();
138   gr_set_mode(GRM_640x480x8, TRUE);
139   screen = gr_alloc_screen(grd_cap->w, grd_cap->h);
140   gr_set_screen(screen);
141   SetRect(&gMovieRect, 0, 0, 600, 300);
142 
143   //---------------------
144   //	Setup the input FileSpecs
145   //---------------------
146   SetInputSpecs();
147 
148   //---------------------
149   // Setup Quicktime stuff.
150   //---------------------
151   if (EnterMovies() != noErr) // Start up the movie tools
152   {
153     ParamText("Can't startup QuickTime.", "", "", "");
154     StopAlert(1000, nil);
155     CleanupAndExit();
156   }
157 
158   //----------------------
159   //	Open the input QuickTime movie.
160   //----------------------
161   fp = fopen(gInputMov, "rb");
162   if (fp == NULL)
163     CheckError(1, "Can't open the input movie!!");
164 
165   dbuffLen = 64000;
166   dbuff = (uchar *)malloc(dbuffLen);
167   /*
168           //----------------------
169           //	Open the input Sound file.
170           //----------------------
171           sndResNum = FSpOpenResFile(&gInputSnd, fsRdPerm);
172           if (sndResNum == -1)
173                   CheckError(1, "Can't open the sound file!!");
174   */
175   //----------------------
176   //	Open the input Palettes file, read the palette changes, and set the first palette.
177   //----------------------
178   palResNum = FSpOpenResFile(&gInputPal, fsRdPerm);
179   if (palResNum == -1)
180     CheckError(1, "Can't open the palette file!!");
181   palChgHdl = GetResource('pchg', 128);
182   if (!palChgHdl)
183     CheckError(1, "Can't load the palette changes resource!!");
184   SetPalette(128);
185 
186   // Allocate a description handle for the movies.
187   gImageDescriptionH = (ImageDescription **)NewHandle(sizeof(ImageDescription));
188   CheckError(MemError(), "Can't alloc description for video.");
189 
190   // Create the first movie.
191   CreateAMovie();
192 
193   //-----------------------------------
194   //	Get the Chunk Offset and Sample-to-Time tables.
195   //-----------------------------------
196   while (TRUE) {
197     if (!QuikReadChunkHdr(fp, &chunkHdr))
198       break;
199     if (chunkHdr.length == 0)
200       break;
201     pinfo = QuikFindChunkInfo(&chunkHdr);
202     if (pinfo->isleaf) {
203       if ((chunkHdr.ctype != QT_MDAT)) {
204         if (chunkHdr.length > dbuffLen) {
205           dbuffLen = chunkHdr.length;
206           dbuff = (uchar *)realloc(dbuff, dbuffLen);
207         }
208         fread(dbuff, chunkHdr.length - sizeof(QT_ChunkHdr), 1, fp);
209 
210         // For the sample-to-time table, create our own table giving a time
211         // for each frame.
212         if (chunkHdr.ctype == QT_STTS) {
213           QTS_STTS *p = (QTS_STTS *)dbuff;
214           short i, j, si;
215 
216           gSampleTimes = (ulong *)malloc(1200 * sizeof(ulong));
217           si = 0;
218           for (i = 0; i < p->numEntries; i++)
219             for (j = 0; j < p->time2samp[i].count; j++)
220               gSampleTimes[si++] = p->time2samp[i].duration;
221         }
222 
223         // For the chunk offsets table, read it in to a memory block.
224         if (chunkHdr.ctype == QT_STCO) {
225           QTS_STCO *p = (QTS_STCO *)dbuff;
226 
227           gNumFrames = p->numEntries;
228           gChunkOffsets = (ulong *)malloc(p->numEntries * sizeof(ulong));
229           BlockMove(p->offset, gChunkOffsets, p->numEntries * sizeof(ulong));
230         }
231       } else
232         QuikSkipChunk(fp, &chunkHdr);
233     }
234   }
235 
236   //----------------------------
237   //	Show the movie, one frame at a time.
238   //----------------------------
239   {
240     long f, line;
241     Ptr frameBuff;
242     Ptr imgp, scrp;
243     RGBColor black = {0, 0, 0};
244     RGBColor white = {0xffff, 0xffff, 0xffff};
245     char buff[64];
246     PalChange *pc;
247 
248     frameBuff = malloc(600 * 300);
249     CheckError(MemError(), "Can't allocate a frame buffer for input movie.");
250 
251     SetRect(&r, 0, -17, 300, 0);
252     for (f = 0; f < gNumFrames; f++) {
253       fseek(fp, gChunkOffsets[f], SEEK_SET);
254       fread(frameBuff, 600 * 300, 1, fp);
255 
256       // Check for a palette change.  If one occurs, then finish up the current movie,
257       // change the palette, then open a new movie.
258       HLock(palChgHdl);
259       pc = (PalChange *)*palChgHdl;
260       while (pc->frameNum) {
261         if (pc->frameNum == f) {
262           EndAMovie();
263           SetPalette(pc->palID);
264           CreateAMovie();
265           break;
266         }
267         pc++;
268       }
269       HUnlock(palChgHdl);
270 
271       imgp = frameBuff;
272       scrp = gScreenAddress;
273       for (line = 0; line < 300; line++) {
274         BlockMove(imgp, scrp, 600);
275         imgp += 600;
276         scrp += gScreenRowbytes;
277       }
278 
279       // Display the frame number.
280 
281       sprintf(buff, "Frame: %d", f);
282       RGBForeColor(&black);
283       PaintRect(&r);
284       MoveTo(1, -6);
285       RGBForeColor(&white);
286       DrawText(buff, 0, strlen(buff));
287 
288       // Add the frame to the QuickTime movie.
289 
290       HLock(gCompressedFrameBitsH);
291       result = CompressSequenceFrame(gSeq, ((CGrafPort *)(gMainWindow))->portPixMap, &gMovieRect, kPrevious,
292                                      *gCompressedFrameBitsH, &compressedFrameSize, 0L, 0L);
293       CheckError(result, "Can't compress a frame.");
294       HUnlock(gCompressedFrameBitsH);
295 
296       result = AddMediaSample(gMedia, gCompressedFrameBitsH, 0L, compressedFrameSize, gSampleTimes[f],
297                               (SampleDescriptionHandle)gImageDescriptionH, 1L, 0, 0L);
298       CheckError(result, "Can't add the frame sample.");
299     }
300   }
301 
302   EndAMovie();
303 
304   if (gImageDescriptionH)
305     DisposeHandle((Handle)gImageDescriptionH);
306 
307   CloseResFile(palResNum);
308   CloseResFile(sndResNum);
309   fclose(fp);
310   ExitMovies();
311 
312   gr_clear(0xFF);
313   CleanupAndExit();
314 }
315 
316 //------------------------------------------------------------------------
317 //  Exit in case of an error.
318 //------------------------------------------------------------------------
CheckError(OSErr error,Str255 displayString)319 void CheckError(OSErr error, Str255 displayString) {
320   if (error == noErr)
321     return;
322   ParamText(displayString, "", "", "");
323   StopAlert(1000, nil);
324   ExitMovies();
325   CleanupAndExit();
326 }
327 
328 //------------------------------------------------------------------------
329 //  Setup the input file specs.
330 //------------------------------------------------------------------------
SetInputSpecs(void)331 void SetInputSpecs(void) {
332   short vRefNum;
333   long temp, parID;
334 
335   GetWDInfo(gMainVRef, &vRefNum, &parID, &temp);
336 
337   gInputPal.vRefNum = vRefNum;
338   gInputPal.parID = parID;
339 
340   gInputSnd.vRefNum = vRefNum;
341   gInputSnd.parID = parID;
342 }
343 
344 //------------------------------------------------------------------------
345 //  Load in the 'mpal' resource and set it.
346 //------------------------------------------------------------------------
SetPalette(short palID)347 void SetPalette(short palID) {
348   Handle gPalHdl;
349 
350   gPalHdl = GetResource('mpal', palID);
351   if (!gPalHdl) {
352     ParamText("Can't load a palette resource!!", "", "", "");
353     StopAlert(1000, nil);
354     ExitMovies();
355     CleanupAndExit();
356   }
357   gr_clear(0xFF);
358   HLock(gPalHdl);
359   gr_set_pal(0, 256, (uchar *)*gPalHdl);
360   HUnlock(gPalHdl);
361 }
362 
363 //------------------------------------------------------------------------
364 //  Create a new movie file, and get the movie ready to add video samples.
365 //------------------------------------------------------------------------
CreateAMovie(void)366 void CreateAMovie(void) {
367   OSErr result;
368   Point dlgPos = {120, 120};
369   SFReply sfr;
370   FSSpec mySpec;
371   Str255 name = "Output Movie";
372   long maxCompressedFrameSize;
373 
374   SFPutFile(dlgPos, "Save Movie as:", name, 0L, &sfr);
375   if (!sfr.good) {
376     ExitMovies();
377     CleanupAndExit();
378   }
379 
380   ClearMoviesStickyError();
381   FSMakeFSSpec(sfr.vRefNum, 0, sfr.fName, &mySpec);
382   result = CreateMovieFile(&mySpec, 'TVOD', 0, createMovieFileDeleteCurFile, &gMovieResNum, &gMovie);
383   CheckError(result, "Can't create output movie file.");
384 
385   SetMovieColorTable(gMovie, gMainColorHand);
386 
387   // Add the sound track here.
388   //	MyCreateTextTrack(gMovie);
389 
390   gTrack = NewMovieTrack(gMovie, 600L << 16, 300L << 16, 0);
391   CheckError(GetMoviesError(), "New video track.");
392 
393   gMedia = NewTrackMedia(gTrack, VideoMediaType, 30, 0L, 0L);
394   CheckError(GetMoviesError(), "New Media for video track.");
395 
396   BeginMediaEdits(gMedia); // We do this since we are adding samples to the media
397   GetMaxCompressionSize(((CGrafPort *)(gMainWindow))->portPixMap, &gMovieRect, 8, spatialQ, codec, codecType,
398                         &maxCompressedFrameSize);
399 
400   gCompressedFrameBitsH = NewHandle(maxCompressedFrameSize);
401   CheckError(MemError(), "Can't allocate output frame buffer.");
402 
403   result = CompressSequenceBegin(&gSeq, ((CGrafPort *)(gMainWindow))->portPixMap, 0L, &gMovieRect, 0L, 8, codec,
404                                  codecType, spatialQ, temporalQ, 15, 0L, kPrevious, gImageDescriptionH);
405   CheckError(result, "Can't begin sequence.");
406 }
407 
408 //------------------------------------------------------------------------
409 //  We're finished writing to the movie, so save the video track and close it down.
410 //------------------------------------------------------------------------
EndAMovie(void)411 void EndAMovie(void) {
412   OSErr result;
413 
414   CDSequenceEnd(gSeq);
415   EndMediaEdits(gMedia);
416 
417   result = InsertMediaIntoTrack(gTrack, 0L, 0L, GetMediaDuration(gMedia), 1L << 16);
418   CheckError(result, "Can't insert media into track.");
419 
420   // Finally, we're done with the movie.
421   result = AddMovieResource(gMovie, gMovieResNum, 0L, 0L);
422   CheckError(result, "Can't add the movie resource.");
423 
424   CloseMovieFile(gMovieResNum);
425 
426   if (gCompressedFrameBitsH)
427     DisposeHandle(gCompressedFrameBitsH);
428 
429   if (gMovie)
430     DisposeMovie(gMovie);
431 }
432 
433 //	===============================================================
434 //	QuickTime file dumping routines.
435 //	===============================================================
436 
437 //	--------------------------------------------------------------
438 //
439 //	QuikReadChunkHdr() reads in the next chunk header, returns TRUE if ok.
440 
QuikReadChunkHdr(FILE * fp,QT_ChunkHdr * phdr)441 uchar QuikReadChunkHdr(FILE *fp, QT_ChunkHdr *phdr) {
442   fread(phdr, sizeof(QT_ChunkHdr), 1, fp);
443 
444   switch (phdr->ctype) {
445   case QT_TRAK:
446     currTrackType = TRACK_OTHER;
447     break;
448 
449   case QT_VMHD:
450     currTrackType = TRACK_VIDEO;
451     break;
452 
453   case QT_SMHD:
454     currTrackType = TRACK_AUDIO;
455     break;
456   }
457 
458   return (feof(fp) == 0);
459 }
460 
461 //	--------------------------------------------------------------
462 //
463 //	QuikFindChunkInfo() finds info for a chunk.
464 
QuikFindChunkInfo(QT_ChunkHdr * phdr)465 QT_ChunkInfo *QuikFindChunkInfo(QT_ChunkHdr *phdr) {
466   static QT_Ctype lastType = 0;
467   static QT_ChunkInfo *lastInfoPtr = NULL;
468 
469   QT_ChunkInfo *pinfo;
470 
471   if (lastType == phdr->ctype)
472     return ((QT_ChunkInfo *)lastInfoPtr);
473 
474   pinfo = chunkInfo;
475   while (pinfo->ctype) {
476     if (pinfo->ctype == phdr->ctype) {
477       lastType = phdr->ctype;
478       lastInfoPtr = pinfo;
479       return ((QT_ChunkInfo *)pinfo);
480     }
481     ++pinfo;
482   }
483   return NULL;
484 }
485 
486 //	--------------------------------------------------------------
487 //
488 //	QuikSkipChunk() skips over the data in the current chunk.
489 
QuikSkipChunk(FILE * fp,QT_ChunkHdr * phdr)490 void QuikSkipChunk(FILE *fp, QT_ChunkHdr *phdr) { fseek(fp, phdr->length - sizeof(QT_ChunkHdr), SEEK_CUR); }
491 
492   //================================================================
493   //	QuickTime Sound Track routines.
494   //================================================================
495 
496 #define kSoundSampleDuration 1
497 #define kSyncSample 0
498 #define kTrackStart 0
499 #define kMediaStart 0
500 #define kFix1 0x00010000
501 
502 //----------------------------------------------------------------
CreateMySoundTrack(Movie theMovie)503 void CreateMySoundTrack(Movie theMovie) {
504   Track theTrack;
505   Media theMedia;
506   Handle sndHandle = nil;
507   SoundDescriptionHandle sndDesc = nil;
508   long sndDataOffset;
509   long sndDataSize;
510   long numSamples;
511   OSErr err = noErr;
512 
513   sndHandle = GetIndResource('snd ', 1);
514   CheckError(ResError(), "GetResource 'snd '");
515   if (sndHandle == nil)
516     return;
517 
518   sndDesc = (SoundDescriptionHandle)NewHandle(4);
519   CheckError(MemError(), "NewHandle for SoundDesc");
520 
521   CreateSoundDescription(sndHandle, sndDesc, &sndDataOffset, &numSamples, &sndDataSize);
522 
523   theTrack = NewMovieTrack(theMovie, 0, 0, kFullVolume);
524   CheckError(GetMoviesError(), "New Sound Track");
525 
526   theMedia = NewTrackMedia(theTrack, SoundMediaType, FixRound((**sndDesc).sampleRate), nil, 0);
527   CheckError(GetMoviesError(), "New Media snd.");
528 
529   err = BeginMediaEdits(theMedia);
530   CheckError(err, "BeginMediaEdits snd.");
531 
532   err = AddMediaSample(theMedia, sndHandle, sndDataOffset, sndDataSize, 1, (SampleDescriptionHandle)sndDesc, numSamples,
533                        0, nil);
534   CheckError(err, "AddMediaSample snd.");
535 
536   err = EndMediaEdits(theMedia);
537   CheckError(err, "EndMediaEdits snd.");
538 
539   err = InsertMediaIntoTrack(theTrack, 0, 0, GetMediaDuration(theMedia), kFix1);
540   CheckError(err, "InsertMediaIntoTrack snd.");
541 
542   if (sndDesc != nil)
543     DisposeHandle((Handle)sndDesc);
544 }
545 
546 //----------------------------------------------------------------
CreateSoundDescription(Handle sndHandle,SoundDescriptionHandle sndDesc,long * sndDataOffset,long * numSamples,long * sndDataSize)547 void CreateSoundDescription(Handle sndHandle, SoundDescriptionHandle sndDesc, long *sndDataOffset, long *numSamples,
548                             long *sndDataSize) {
549   long sndHdrOffset = 0;
550   long sampleDataOffset;
551   SoundHeaderPtr sndHdrPtr = nil;
552   long numFrames;
553   long samplesPerFrame;
554   long bytesPerFrame;
555   SignedByte sndHState;
556   SoundDescriptionPtr sndDescPtr;
557 
558   *sndDataOffset = 0;
559   *numSamples = 0;
560   *sndDataSize = 0;
561 
562   SetHandleSize((Handle)sndDesc, sizeof(SoundDescription));
563   CheckError(MemError(), "SetHandleSize for sndDesc.");
564 
565   sndHdrOffset = GetSndHdrOffset(sndHandle);
566   if (sndHdrOffset == 0)
567     CheckError(-1, "GetSndHdrOffset ");
568 
569   // we can use pointers since we don't move memory
570   sndHdrPtr = (SoundHeaderPtr)(*sndHandle + sndHdrOffset);
571   sndDescPtr = *sndDesc;
572   sndDescPtr->descSize = sizeof(SoundDescription); // total size of sound desc structure
573   sndDescPtr->resvd1 = 0;
574   sndDescPtr->resvd2 = 0;
575   sndDescPtr->dataRefIndex = 1;
576   sndDescPtr->compressionID = 0;
577   sndDescPtr->packetSize = 0;
578   sndDescPtr->version = 0;
579   sndDescPtr->revlevel = 0;
580   sndDescPtr->vendor = 0;
581 
582   switch (sndHdrPtr->encode) {
583   case stdSH:
584     sndDescPtr->dataFormat = 'raw ';                // uncompressed offset-binary data
585     sndDescPtr->numChannels = 1;                    // number of channels of sound
586     sndDescPtr->sampleSize = 8;                     // number of bits per sample
587     sndDescPtr->sampleRate = sndHdrPtr->sampleRate; // sample rate
588     *numSamples = sndHdrPtr->length;
589     *sndDataSize = *numSamples;
590     bytesPerFrame = 1;
591     samplesPerFrame = 1;
592     sampleDataOffset = (Ptr)&sndHdrPtr->sampleArea - (Ptr)sndHdrPtr;
593     break;
594 
595   case extSH: {
596     ExtSoundHeaderPtr extSndHdrP = (ExtSoundHeaderPtr)sndHdrPtr;
597 
598     sndDescPtr->dataFormat = 'raw ';                   // uncompressed offset-binary data
599     sndDescPtr->numChannels = extSndHdrP->numChannels; // number of channels of sound
600     sndDescPtr->sampleSize = extSndHdrP->sampleSize;   // number of bits per sample
601     sndDescPtr->sampleRate = extSndHdrP->sampleRate;   // sample rate
602     numFrames = extSndHdrP->numFrames;
603     *numSamples = numFrames;
604     bytesPerFrame = extSndHdrP->numChannels * (extSndHdrP->sampleSize / 8);
605     samplesPerFrame = 1;
606     *sndDataSize = numFrames * bytesPerFrame;
607     sampleDataOffset = (Ptr)(&extSndHdrP->sampleArea) - (Ptr)extSndHdrP;
608   } break;
609 
610   default:
611     CheckError(-1, "Corrupt sound data or unsupported format.");
612     break;
613   }
614   *sndDataOffset = sndHdrOffset + sampleDataOffset;
615 }
616 
617 //----------------------------------------------------------------
618 typedef SndCommand *SndCmdPtr;
619 
620 typedef struct {
621   short format;
622   short numSynths;
623 } Snd1Header, *Snd1HdrPtr, **Snd1HdrHndl;
624 
625 typedef struct {
626   short format;
627   short refCount;
628 } Snd2Header, *Snd2HdrPtr, **Snd2HdrHndl;
629 typedef struct {
630   short synthID;
631   long initOption;
632 } SynthInfo, *SynthInfoPtr;
633 
GetSndHdrOffset(Handle sndHandle)634 long GetSndHdrOffset(Handle sndHandle) {
635   short howManyCmds;
636   long sndOffset = 0;
637   Ptr sndPtr;
638 
639   if (sndHandle == nil)
640     return 0;
641   sndPtr = *sndHandle;
642   if (sndPtr == nil)
643     return 0;
644 
645   if ((*(Snd1HdrPtr)sndPtr).format == firstSoundFormat) {
646     short synths = ((Snd1HdrPtr)sndPtr)->numSynths;
647     sndPtr += sizeof(Snd1Header) + (sizeof(SynthInfo) * synths);
648   } else {
649     sndPtr += sizeof(Snd2Header);
650   }
651 
652   howManyCmds = *(short *)sndPtr;
653 
654   sndPtr += sizeof(howManyCmds);
655 
656   // sndPtr is now at the first sound command--cruise all
657   // commands and find the first soundCmd or bufferCmd
658 
659   while (howManyCmds > 0) {
660     switch (((SndCmdPtr)sndPtr)->cmd) {
661     case (soundCmd + dataOffsetFlag):
662     case (bufferCmd + dataOffsetFlag):
663       sndOffset = ((SndCmdPtr)sndPtr)->param2;
664       howManyCmds = 0; // done, get out of loop
665       break;
666     default: // catch any other type of commands
667       sndPtr += sizeof(SndCommand);
668       howManyCmds--;
669       break;
670     }
671   }
672 
673   return sndOffset;
674 }
675 
676 /*	DEATH MOVIE
677 
678         // English.
679         char					theText1[] = "They find your body and give it new life.";
680         char					theText2[] = "As a cyborg, you will serve SHODAN well.";
681         // German.
682         char					theText1[] = "Sie finden deine Leiche und geben ihr neues Leben.";
683         char					theText2[] = "Als Cyborg wirst du SHODAN treu dienen.";
684         // French.
685         char					theText1[] = "Ils ont trouv ton corps et lui ont redonn la vie.";
686         char					theText2[] = "En tant que cyborgue, tu serviras bien SHODAN.";
687 
688         // Timing - 507 total
689         blank	9
690         text 1	150
691         blank	17
692         text 2	331
693 */
694 
695 /*	ENDGAME MOVIE
696 
697         // English.
698         char					theText1[] = "It's over.";
699         char					theText2[] = "They offered you a nice, boring job at Trioptimum; it
700    never occured to you to take it."; char					theText3[] = "Old habits die hard.";
701         // German.
702         char					theText1[] = "Es ist vorbei.";
703         char					theText2[] = "Sie haben dir einen netten Job bei Triop angeboten; es
704    w„re dir nie eingefallen, ihr Angebot anzunehmen."; char					theText3[] = "Du
705    kannst es eben einfach nicht lassen.";
706         // French.
707         char					theText1[] = "C'est fini.";
708         char					theText2[] = "Ils vous ont offert un bon boulot ennuyeux sur Triop;
709    ‡a ne vous est jamais venu … l'ide de l'accepter.";
710         char					theText3[] = "On a du mal … se dbarasser des mauvaises habitudes.";
711 
712         // Timing - 507 total
713         blank	9
714         text 1	180
715         blank	17
716         text 2	176
717         blank	30
718         text 2	1021
719 */
MyCreateTextTrack(Movie theMovie)720 void MyCreateTextTrack(Movie theMovie) {
721   Track theTrack;
722   Media theMedia;
723   OSErr err = noErr;
724   char blankText[] = " ";
725   char theText1[] = "Ils ont trouv ton corps et lui ont redonn la vie.";
726   char theText2[] = "En tant que cyborgue, tu serviras bien SHODAN.";
727   RGBColor black = {0, 0, 0};
728   RGBColor white = {0xeeee, 0xeeee, 0xeeee};
729   Rect textBox;
730   TimeValue retTime;
731 
732   SetRect(&textBox, 0, 320, 600, 350);
733   theTrack = NewMovieTrack(theMovie, 600L << 16, 350L << 16, 0);
734   CheckError(GetMoviesError(), "New text track.");
735 
736   theMedia = NewTrackMedia(theTrack, TextMediaType, 30, 0L, 0L);
737   CheckError(GetMoviesError(), "New Media for text track.");
738 
739   err = BeginMediaEdits(theMedia);
740   CheckError(err, "BeginMediaEdits: text.");
741 
742   err = AddTextSample(GetMediaHandler(theMedia), (Ptr)blankText, strlen(blankText), geneva, 14, bold, &white, &black,
743                       teCenter, &textBox, 0, 0, 0, 0, nil, 9, &retTime);
744   CheckError(err, "AddTextSample 1.");
745   err = AddTextSample(GetMediaHandler(theMedia), (Ptr)theText1, strlen(theText1), geneva, 14, bold, &white, &black,
746                       teCenter, &textBox, 0, 0, 0, 0, nil, 150, &retTime);
747   CheckError(err, "AddTextSample 2.");
748   err = AddTextSample(GetMediaHandler(theMedia), (Ptr)blankText, strlen(blankText), geneva, 14, bold, &white, &black,
749                       teCenter, &textBox, 0, 0, 0, 0, nil, 17, &retTime);
750   CheckError(err, "AddTextSample 3.");
751   err = AddTextSample(GetMediaHandler(theMedia), (Ptr)theText2, strlen(theText2), geneva, 14, bold, &white, &black,
752                       teCenter, &textBox, 0, 0, 0, 0, nil, 331, &retTime);
753   CheckError(err, "AddTextSample 4.");
754 
755   err = EndMediaEdits(theMedia);
756   CheckError(err, "EndMediaEdits: text.");
757 
758   err = InsertMediaIntoTrack(theTrack, 0, 0, GetMediaDuration(theMedia), kFix1);
759   CheckError(err, "InsertMediaIntoTrack: text.");
760 }
761