home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Audio-DSP / NU / Source / GlyphView.m < prev    next >
Encoding:
Text File  |  1993-01-05  |  16.7 KB  |  663 lines

  1. #import "GlyphView.h"
  2. #import "MenuManager.h"
  3. #import "ClassManager.h"
  4. #import "Terminator.h"
  5. #import "Tray.h"
  6. #import "MenuManager.h"
  7. #import "InstanceManager.h"
  8. #import "GlyphManager.h"
  9. #import "NuPerformer.h"
  10. #import "MKManager.h"
  11. #import "FGWindow.h"
  12. #import <appkit/Application.h>
  13. #import <appkit/Window.h>
  14. #import <appkit/NXCursor.h>
  15. #import <appkit/NXImage.h>
  16. #import <appkit/NXBrowser.h>
  17. #import <appkit/TextField.h>
  18. #import <appkit/ButtonCell.h>
  19. #import <appkit/SavePanel.h>
  20. #import <defaults/defaults.h>
  21. #import <appkit/publicWraps.h>
  22. #import <musickit/Conductor.h>
  23. #import <musickit/Orchestra.h>
  24. #import <dpsclient/psops.h>
  25. #import <dpsclient/wraps.h>
  26. #import <c.h>
  27. #import <strings.h>
  28. #import <math.h>
  29.  
  30. #define GLYPHVIEWVERSION 1
  31.  
  32. extern id Nu ;
  33. static id glyphViewList ;
  34. const char * glyphTypes[] = {"m",NULL} ;
  35.  
  36. void NuClearRect(NXRect * aRect) 
  37. { // use this to clear rectangles
  38.   PSsetgray(1.0) ;
  39.   PSsetalpha(0.0) ;
  40.   PSrectfill(aRect->origin.x,aRect->origin.y,
  41.     aRect->size.width,aRect->size.height) ;
  42.   PSsetgray(0.0) ;
  43.   PSsetalpha(1.0) ;
  44. }
  45.  
  46. @implementation GlyphView: View
  47. { @public
  48.   id glyphMsgView, mouseObject ;
  49.   id playButton, freezeButton, lockButton, resizeButton ;
  50.   Glyph *rootGlyph ;
  51.   struct glyphViewFlags flags ;
  52.   char *fileName ;
  53.   // music kit data
  54.   double beatsPerMinute, headRoom, deltaT, samplingRate, tickPeriod ;
  55.   BOOL fastResponse ;
  56. }
  57.  
  58. + become: (List *) newIds if: (List *) oldIds views: viewList;
  59. { // if any existing glyphs are members of oldClass, then
  60.   // they are "coerced" into newClass by changing their
  61.   // "isa" pointers. viewList should be a List object:
  62.   // I add the ids of all glyphViews containing Glyphs whose
  63.   // class changed to this list..
  64.   int viewNum, viewKnt ;
  65.   id theView, theGlyph ;
  66.   viewKnt = [glyphViewList count] ;
  67.   for(viewNum = 0 ; viewNum < viewKnt ; viewNum++)
  68.   { theView = [glyphViewList objectAt: viewNum] ;
  69.     theGlyph = [theView rootGlyph] ;
  70.     if([self glyph: theGlyph become: newIds if: oldIds])
  71.       [viewList addObject: theView] ;
  72.   }
  73.   return self ;
  74. }
  75.  
  76. + cursor ;
  77. { // draws and sets the Nutation cursor
  78.   static id cursor = nil ;
  79.   if(!cursor)
  80.   { id anImage ;
  81.     NXSize aSize = {16.0,16.0} ;
  82.     NXPoint hotSpot = {7.0,7.0} ;
  83.     anImage = [[NXImage alloc] initSize: &aSize] ;
  84.     [anImage lockFocus] ;
  85.     PSsendstring(
  86.       "0 setalpha "
  87.      "1 setgray "
  88.      "0 0 16 16 rectfill "
  89.      "1 setalpha "
  90.      "0 setgray "
  91.      "1 setlinewidth "
  92.      "3 3 moveto "
  93.      "6 6 lineto "
  94.      "3 2 moveto "
  95.      "5 4 lineto "
  96.      "2 3 moveto "
  97.      "4 5 lineto "
  98.       "9 9 moveto "
  99.      "12 12 lineto "
  100.      "11 10 moveto "
  101.      "13 12 lineto "
  102.      "10 11 moveto "
  103.      "12 13 lineto "
  104.      "3 12 moveto "
  105.      "5 10 lineto "
  106.       "3 13 moveto "
  107.       "4 12 lineto "
  108.       "2 12 moveto "
  109.       "3 11 lineto "
  110.      "9 6 moveto "
  111.      "11 4 lineto "
  112.      "11 5 moveto "
  113.      "12 4 lineto "
  114.      "10 4 moveto "
  115.      "11 3 lineto "
  116.      "stroke ") ;              
  117.     PScvx() ;
  118.     PSexec() ;
  119.     [anImage unlockFocus] ;
  120.     cursor = [[NXCursor alloc] initFromImage: anImage] ;
  121.     [cursor setHotSpot: &hotSpot] ;
  122.   }
  123.   return cursor ;
  124. }
  125.  
  126. // need this to get ahold of glyph's "private"
  127. // isa instance var
  128. struct glyphDef
  129. { @defs(Glyph)
  130. } ;
  131.  
  132. + (BOOL) glyph: (Glyph *) aGlyph become: (List *) newList if: (List *) oldList ;
  133. { // if aGlyph->isa is in oldList, change it to 
  134.   // corresponding value on newList.
  135.   // This should only be called on the root of a TTree.
  136.   // It recurses through all Glyphs in the tree, and
  137.   // returns YES iff at least one Glyph in the
  138.   // tree "became" a value on newList.
  139.   // Also descends the super_class chain to see if a super_class
  140.   // needs to be replaced.
  141.   BOOL rval = NO ;
  142.   struct glyphDef *theGlyph ;
  143.   struct objc_class *theClass ;
  144.   int i, knt ;
  145.   knt = [newList count] ;
  146.   if(aGlyph->flags.isTerminator)
  147.     return NO ;
  148.   theGlyph = (struct glyphDef *) aGlyph ;
  149.   for(i = 0 ; i < knt ; i++)
  150.   { if(theGlyph->isa == (Class) [oldList objectAt: i])
  151.     { theGlyph->isa = (Class) [newList objectAt: i] ;
  152.       rval = YES ;
  153.       break ;
  154.     }
  155.   }
  156.   if(!rval) // search up superclass chain
  157.   { theClass = theGlyph->isa ;
  158.     while(theClass->super_class)
  159.     { for(i = 0 ; i < knt ; i++)
  160.       { if(theClass->super_class == (Class) [oldList objectAt: i])
  161.         { theClass->super_class = (Class) [newList objectAt: i] ;
  162.           rval = YES ;
  163.       break ;
  164.         }
  165.       }
  166.       if(rval)
  167.         break ;
  168.       theClass = theClass->super_class ;
  169.     }
  170.   }
  171.   rval = [self glyph: aGlyph->is become: newList if: oldList] || rval ;
  172.   rval = [self glyph: aGlyph->then become: newList if: oldList] || rval ;
  173.   return rval ;
  174. }
  175.  
  176. + glyphViewList ;
  177. { // return the list of glyph views
  178.   return glyphViewList ;
  179. }
  180.  
  181. + initialize ;
  182. { [self setVersion: GLYPHVIEWVERSION] ;
  183.   glyphViewList = [[List alloc] init] ;
  184.   return self ;
  185. }
  186.  
  187. - (BOOL) acceptsFirstResponder ;
  188. { return YES ;
  189. }
  190.  
  191. - browse: sender ;
  192. { // open a ClassManager on the targetGlyph
  193.   // Keep this in glyphView
  194.   char *aClass = [[[Nu targetGlyph] class] name] ;
  195.   if(! [[Nu glyphManager] editFile: nil class: aClass])
  196.   NXRunAlertPanel("Nu","Error: can't find class definition "
  197.     "for class %s\n", NULL,NULL,NULL,aClass) ;
  198.   return self ;
  199. }
  200.  
  201. - clearGlyphMsgView: aView ;
  202. { if(glyphMsgView)
  203.     [glyphMsgView setStringValue: ""] ;
  204.   return self ;
  205. }
  206.  
  207. -displayRaised: sender ;
  208. { return [rootGlyph displayRaised] ;
  209. }
  210.  
  211. - drawSelf:(const NXRect *)rects :(int)rectCount ;
  212. { if(NXDrawingStatus == NX_PRINTING)
  213.     return [rootGlyph printSelf: rects :rectCount] ;
  214.   else
  215.     return [rootGlyph drawSelf: rects :rectCount] ;
  216. }
  217.  
  218. - fileName: (char *) fname ;
  219. { // copy fname into ivar, set the
  220.   // window's title to fname, with the
  221.   // filename followed by the path
  222.   char fullName[128], *name ;
  223.   if(fileName)
  224.     free(fileName) ;
  225.   fileName = (char *) malloc(strlen(fname) + 3) ;
  226.   strcpy(fileName, fname) ;
  227.   strcpy(fullName, fileName) ;
  228.   name = rindex(fullName,'/') ;
  229.   if(name != NULL) // is there is a path,
  230.   { char title[129] ;
  231.     name[0] = '\0' ; // split string into 2 pieces
  232.     name++ ;
  233.     sprintf(title,"%s  %s",name,fullName) ;
  234.     [window setTitle: title] ;
  235.   }
  236.   else // no path, just use the filename
  237.     [window setTitle: fname] ;
  238.   return self ;
  239. }
  240.  
  241. - free ;
  242. { free(fileName) ;
  243.   return [super free] ;
  244. }
  245.  
  246. - freezeButton ;
  247. { return freezeButton ;
  248. }
  249.  
  250. - freezeButton: sender ;
  251. { // freeze button pressed...
  252.   // Just relay this to the rootGlyph
  253.   [rootGlyph freezeButton: freezeButton] ;
  254.   return self ;
  255. }
  256.  
  257.  
  258. - glyphMsg: (char *) aMsg ;
  259. { // flash the msg in my message view
  260.   if(glyphMsgView)
  261.   { [glyphMsgView setStringValue: aMsg] ;
  262.    [self perform: @selector(clearGlyphMsgView:) with: self
  263.         afterDelay: 1500 cancelPrevious: YES] ;
  264.   }
  265.   return self ;
  266. }
  267.  
  268. - inspect: sender ;
  269. { // open an instance manager on target glyph
  270.   // all the work is handled by the menuManager
  271.   return [Nu instance: self] ;
  272. }
  273.  
  274. - lock: sender ;
  275. { // relay this action message
  276.   [rootGlyph lock] ;
  277.   return self ;
  278. }
  279.  
  280. - lockButton ;
  281. { // return id of the lock button
  282.   return lockButton ;
  283. }
  284.  
  285. - mouseDown: (NXEvent *) anEvent ;
  286. { // normally, mouseObject is set to
  287.   // the rootGlyph, but it is set to
  288.   // the inspectorManager during resizing.
  289.   return [mouseObject mouseDown: anEvent] ;
  290. }
  291.  
  292. - mouseDragged: (NXEvent *) anEvent ;
  293. { [window setDocEdited: YES] ;
  294.   return [mouseObject mouseDragged: anEvent] ;
  295. }
  296.  
  297. - mouseUp: (NXEvent *) anEvent ;
  298. { return [mouseObject mouseUp: anEvent] ;
  299. }
  300.  
  301.  
  302. - play:sender ;
  303. { return [rootGlyph play: playButton] ;
  304. }
  305.  
  306. - resize: sender ;
  307. { // go into (or out of, i.e. toggle) resizing "mode" by changing
  308.   // the mouseObject ivar to divert mouse events
  309.   // to the instanceManager (or, if leaving the mode,
  310.   // by redirecting mouse events to saved mouseObject) 
  311.   id instanceManager = [Nu instanceManager] ;
  312.   if(!flags.resizing) // enter the mode
  313.   { flags.resizing = YES ;
  314.     if([rootGlyph targetGlyph] == rootGlyph)
  315.     { // don't allow resizing or the rootGlyph
  316.       [resizeButton performClick: self] ;
  317.       return self ;
  318.     }
  319.     mouseObject = instanceManager  ; 
  320.     [self setCursor: [[instanceManager class] cursor]] ;
  321.     [instanceManager startSizer: self] ;
  322.   }
  323.   else // exit the mode--replace mouseObject with targetGlyph
  324.   { [instanceManager stopSizer: self] ;
  325.     [self setCursor: [[self class] cursor]] ;
  326.     flags.resizing = NO ;
  327.     mouseObject = rootGlyph ;
  328.   }
  329.   return self ;
  330. }
  331.  
  332.  
  333. - resizeButton ;
  334. { // return id of resizeButton
  335.   return resizeButton ;
  336. }
  337.  
  338.  
  339.  
  340. - revert: sender ;
  341. { int rval ;
  342.   // if in the glyph borwser, or if not edited, just return
  343.   if(!flags.freeWhenClosed || ![window isDocEdited])
  344.     return self ;
  345.   rval = NXRunAlertPanel(
  346.        "Revert","Window has unsaved changes. Revert?",
  347.         "Revert","Don't Revert", NULL,
  348.         fileName ? fileName: "Untitled") ;
  349.   if (rval == NX_ALERTDEFAULT) // then revert...
  350.   { if(fileName)
  351.     { // put fileName into NULL terminated list,
  352.       // get Menu Manager to open it from disk
  353.       char *fileNames[2] ;
  354.       fileNames[0] = fileName ;
  355.       fileNames[1] = NULL ;
  356.       [Nu openList: fileNames path: NULL] ;
  357.     }
  358.     else // file was never named, hence was brand new...
  359.       [Nu newGlyphWindow: self] ;
  360.     // now schedule my own demise
  361.     [window setDocEdited: NO] ;
  362.     [window perform: @selector(performClose:)
  363.         with: self afterDelay:1 cancelPrevious: YES] ;
  364.   }
  365.   // else don't revert
  366.   return self ;
  367. }
  368.  
  369.  
  370. - rightMouseDown: (NXEvent *) anEvent ;
  371. { return [mouseObject rightMouseDown: anEvent] ;
  372. }
  373.  
  374. - rightMouseDragged: (NXEvent *) anEvent ;
  375. { return [mouseObject rightMouseDragged: anEvent] ;
  376. }
  377.  
  378.  
  379. - rightMouseUp: (NXEvent *) anEvent ;
  380. { return [mouseObject rightMouseUp: anEvent] ;
  381. }
  382.  
  383. - rootGlyph ;
  384. { return rootGlyph ;
  385. }
  386.  
  387. - rootGlyph: aGlyph ;
  388. { rootGlyph = aGlyph ;
  389.   return self ;
  390. }
  391.  
  392. -(BOOL) save:sender ;
  393. { if(fileName == NULL) 
  394.     return [self saveAs: sender] ;
  395.   else
  396.     return [self saveToFile] ;
  397.   return YES ;
  398. }
  399.  
  400. -(BOOL) saveAs:sender ;
  401. { id savePanel = [SavePanel new] ;
  402.   [savePanel setRequiredFileType: "Nu"] ;
  403.   if([savePanel runModalForDirectory: NULL file: fileName])
  404.   { [self fileName: (char *) [savePanel filename]] ;
  405.     return [self saveToFile] ;
  406.   }
  407.   else
  408.     return NO ;
  409. }
  410.  
  411.  
  412. -(BOOL) saveToFile ;
  413. { // save my window and all its subviews and
  414.   // subglyphs to fileName
  415.   NXStream *aStream ;
  416.   if(aStream = NXOpenTypedStreamForFile(fileName, NX_WRITEONLY))
  417.   { // make rootGlyph the target before saving
  418.     id targetGlyph = [rootGlyph targetGlyph] ;
  419.     [rootGlyph becomeTarget] ;
  420.     NXWriteRootObject(aStream,window) ;
  421.     NXCloseTypedStream(aStream) ;
  422.     [targetGlyph becomeTarget] ;
  423.     [window setDocEdited: NO] ;
  424.     return YES ;
  425.   }
  426.   return NO ;
  427. }
  428.  
  429.  
  430. - setCursor: aCursor ;
  431. { // make aCursor the cursor to use when mouse
  432.   // is in this view
  433.   [[superview superview] setDocCursor: aCursor] ;
  434.   [window resetCursorRects] ;
  435.   return self ;
  436. }
  437.  
  438.  
  439. /**
  440.  ** music kit manager "set" methods... these methods are
  441.  ** called by the manager on user interaction...they keep
  442.  ** the glyphviews ivars in sync with the manager
  443.  **/
  444.  
  445. - setMKMBeatsPerMinute: (double) bPM ;
  446. { beatsPerMinute =  bPM ;
  447.   return self ;
  448. }
  449.  
  450. - setMKMDeltaT: (double) seconds ;
  451. { deltaT =  seconds ;
  452.   return self ;
  453. }
  454.  
  455. - setMKMFastResponse: (BOOL) YESorNO ;
  456. { fastResponse = YESorNO ;
  457.   return self ;
  458. }
  459.  
  460. - setMKMHeadRoom: (double) seconds ;
  461. { headRoom = seconds ;
  462.   return self ;
  463. }
  464.  
  465. - setMKMTickPeriod: (double) tP ;
  466. { tickPeriod =  tP ;
  467.   return self ;
  468. }
  469.  
  470. - setMKMSamplingRate: (double) sR ;
  471. { samplingRate = sR ;
  472.   return self ;
  473. }
  474.  
  475. /*********/
  476.  
  477. - setFreeWhenClosed: (BOOL) YESorNO ;
  478. { // set whether I free myself and glyphs when
  479.   // my window closes.  default is YES ;
  480.   flags.freeWhenClosed = YESorNO ;
  481.   return self ;
  482. }
  483.  
  484. - setGlyphMsgView: anObject ;
  485. { // by making this method explicit, the GlyphManager
  486.   // can reset the glyphMsgView to its own msgView.
  487.   glyphMsgView = anObject ;
  488.   [Nu glyphView: self] ;
  489.   [self setupNew] ;
  490.   return self ;
  491. }
  492.  
  493.  
  494. - setupArchived
  495. { // perform setup after unarchived
  496.   [rootGlyph setup] ;
  497.   return [self setupCommon] ;
  498. }
  499.  
  500. - setupCommon ;
  501. { // perform setup common to both new and
  502.   // unarchived glyphViews
  503.   id mKM  ;
  504.   [[GlyphView glyphViewList] addObject: self] ;
  505.   [window useOptimizedDrawing: YES] ;
  506.   [window setDocEdited: NO] ;
  507.   [window display] ;
  508.   [self setCursor: [[self class] cursor]] ;
  509.   [rootGlyph display: &bounds from: nil] ;
  510.   [window setMiniwindowIcon: "gwIcon.tiff"] ;
  511.   // get the music kit values, using what
  512.   // is currently displayed in the manager's window
  513.   mKM = [Nu musicKitManager] ;
  514.   beatsPerMinute = [mKM beatsPerMinute] ;
  515.   headRoom = [mKM headRoom] ;
  516.   deltaT = [mKM deltaT] ;
  517.   samplingRate = [mKM samplingRate] ;
  518.   tickPeriod = [mKM tickPeriod] ;
  519.   fastResponse = [mKM fastResponse] ;
  520.   return self ;
  521. }
  522.  
  523. - setupNew ;
  524. { // perform initial setup of a
  525.   // brand new window
  526.   id clipView = superview ;
  527.   flags.freeWhenClosed = YES ; 
  528.   [window addToEventMask: NX_LMOUSEDOWNMASK | NX_RMOUSEDOWNMASK
  529.      | NX_LMOUSEDRAGGEDMASK | NX_RMOUSEDRAGGEDMASK 
  530.      | NX_LMOUSEUPMASK | NX_RMOUSEUPMASK] ;
  531.   // NOTE: this must come BEFORE you alloc init:: the rootGlyph,
  532.   // as the rootGlyph must be able to get my id (via Nu).
  533.   [Nu glyphView: self] ; 
  534.   // setup the rootGlyph and the mouseObject
  535.   rootGlyph = mouseObject = 
  536.     [[objc_getClass("Root") alloc] init:frame.size.width
  537.                  :frame.size.height] ;
  538.   // make sure scrollview autosizes its subviews
  539.   [[clipView superview] setAutoresizeSubviews: YES] ;
  540.   [clipView setAutosizing: NX_HEIGHTSIZABLE | NX_WIDTHSIZABLE] ;
  541.   [clipView setAutoresizeSubviews: YES] ;
  542.   return [self setupCommon] ;
  543. }  
  544.  
  545.  
  546. - sizeTo: (float) width :(float) height ;
  547. { // glyphViews can only grow, they are not
  548.   // allowed to shrink
  549.   NXSize newSize ;
  550.   id clipView = superview ;
  551.   [super sizeTo: newSize.width = MAX(bounds.size.width,width)
  552.               : newSize.height = MAX(bounds.size.height,height)] ;
  553.   [rootGlyph sizeTo: newSize.width :newSize.height] ;
  554.   [[clipView superview] reflectScroll: clipView] ;
  555.   return self ;
  556. }
  557.  
  558. - targetGlyph ;
  559. { return [rootGlyph targetGlyph] ;
  560. }
  561.  
  562. - (int) tag ;
  563. { return GLYPHVIEWTAG ;
  564. }
  565.  
  566.  
  567. - updateMKManager ;
  568. { // copy my music kit data into the MKManager window
  569.   id m ;
  570.   m = [Nu musicKitManager] ;
  571.   [m beatsPerMinute: beatsPerMinute] ;
  572.   [m headRoom: headRoom] ;
  573.   [m deltaT: deltaT] ;
  574.   [m samplingRate: samplingRate] ;
  575.   [m tickPeriod: tickPeriod] ;
  576.   [m fastResponse: fastResponse] ;
  577.   return self ;  
  578. }
  579.  
  580. - windowDidBecomeMain: sender ;
  581. {  // inform Nu that I have become the main glyphView
  582.   [Nu glyphView: self] ;
  583.   // update the music kit defaults
  584.   [self updateMKManager] ;
  585.   return self ;
  586. }
  587.  
  588. - windowDidResignMain: sender ;
  589. { return self ;
  590.  
  591.  
  592. - windowWillClose: sender ;
  593. { // our window is about to close; need to free up 
  594.   // stuff we created if window will be freed 
  595.  
  596.   if(flags.freeWhenClosed)
  597.   { // in the glyph browser's glyphView, flags.freeWhenClosed = NO,
  598.     // i.e. we never free it 
  599.     if([window isDocEdited])
  600.     { int rval = NXRunAlertPanel(
  601.        "Close","Save changes to %s?",
  602.         "Save","Don't Save","Don't Close",
  603.         fileName ? fileName: "Untitled") ;
  604.       if (rval == NX_ALERTOTHER)
  605.         return NO ;
  606.       else if (rval == NX_ALERTDEFAULT)
  607.         [self save: self] ;
  608.       // else just close
  609.     }
  610.     if([Nu inspecting])
  611.       if([[Nu instanceManager] targetGlyph] == [rootGlyph targetGlyph])
  612.         [[Nu instanceManager] orderOut: self] ;
  613.     // clear any delayed message
  614.     // Historical note: in 2.0, afterDelay: 0 did the job.
  615.     // In 3.0, this didn't work, but -1 did.  Go figure.
  616.     [self perform: @selector(clearGlyphMsgView:) with: self
  617.        afterDelay: -1 cancelPrevious: YES] ;
  618.     [rootGlyph basicFree] ;
  619.     [[GlyphView glyphViewList] removeObject: self] ;
  620.     // set current glyphView to that of the glyphManager
  621.     [Nu glyphView: [[Nu glyphManager] glyphView]] ;
  622.   }
  623.   return self ;
  624.  
  625. -read: (NXTypedStream *) stream ;
  626. { int version ;
  627.   version = NXTypedStreamClassVersion(stream,[[self class] name]) ;
  628.   [super read: stream] ;
  629.   [Nu glyphView: self] ;
  630.   glyphMsgView = NXReadObject(stream) ;
  631.   freezeButton = NXReadObject(stream) ;
  632.   resizeButton = NXReadObject(stream) ;
  633.   lockButton = NXReadObject(stream) ;
  634.   playButton = NXReadObject(stream) ;
  635.   rootGlyph = mouseObject = NXReadObject(stream) ;
  636.   NXReadType(stream,"s",(void *) &flags) ;
  637.   // unarchive music kit parameters
  638.   NXReadTypes(stream,"fffffc",&beatsPerMinute,&headRoom,&deltaT,
  639.       &samplingRate,&tickPeriod,&fastResponse) ; 
  640.   [self updateMKManager] ;
  641.   return self ;
  642. }
  643.  
  644. -write: (NXTypedStream *) stream ;
  645. { [super write: stream] ;
  646.   NXWriteObjectReference(stream, glyphMsgView) ;
  647.   NXWriteObjectReference(stream, freezeButton) ;
  648.   NXWriteObjectReference(stream, resizeButton) ;
  649.   NXWriteObjectReference(stream, lockButton) ;
  650.   NXWriteObjectReference(stream, playButton) ;
  651.   NXWriteObject(stream, rootGlyph) ;
  652.   NXWriteType(stream,"s",(void *) &flags) ;
  653.   NXWriteTypes(stream,"fffffc",&beatsPerMinute,&headRoom,&deltaT,
  654.        &samplingRate,&tickPeriod,&fastResponse) ;
  655.   return self ;
  656. }
  657.  
  658. @end
  659.  
  660.  
  661.