home *** CD-ROM | disk | FTP | other *** search
/ Boldly Go Collection / version40.iso / TS / 17A / DCFG203.ZIP / DCFG2.PAS < prev    next >
Pascal/Delphi Source File  |  1992-02-08  |  19KB  |  638 lines

  1.  
  2. {Dirt Cheap Frame Grabber - Version 2.03}
  3. {as of 8 Feb 1992 - by Michael Day}
  4. {public domain}
  5.  
  6. program DCFG2;
  7. uses crt;
  8. const maxframe = 30000;
  9.       maxintrp = 30000;
  10.  
  11. type frametype = array[0..maxframe] of byte;
  12.      frameptr = ^frametype;
  13.      intrptype = array[0..maxintrp] of byte;
  14.      intrpptr = ^intrptype;
  15.      string8 = string[8];
  16.  
  17.      FrameObj = object
  18.        fary : array[0..3] of frameptr;
  19.        iary : intrpptr;
  20.        dary : intrpptr;
  21.        inport : word;      {frame port data input address (video data)}
  22.        outport : word;     {frame port data output address (control)}
  23.        frameport : word;   {printer port number to use for frame grabber}
  24.        grabsize : word;    {size of data to grab from port}
  25.        framenum : byte;    {frame sequence number}
  26.        IntrpWidth : word;  {width of the intrp array (scan width) }
  27.        IntrpSize : word;   {size of the intrp array (width*lines) }
  28.        Filenum:word;       {next file frame number to use}
  29.        DiskFrameSize:word;
  30.        FrameCount:word;
  31.  
  32.        constructor Init;
  33.        destructor Done;
  34.        procedure SetFramePort(what:string8);
  35.        function  GrabFrame(inprt,size:word; Fptr:frameptr):boolean;
  36.        function  GrabOne:boolean;
  37.        procedure F2IConvert(Fnum:byte; GSize,IWidth,ISize:word;
  38.                             Iptr:IntrpPtr; Fptr:FramePtr);
  39.        procedure IntrpDisplay(fnum,IWidth,ISize:word; Iptr:IntrpPtr);
  40.        procedure MakeDiskArray(fnum,IWidth,ISize:word;
  41.                                     Iptr:IntrpPtr; Dptr:IntrpPtr);
  42.      end;
  43.  
  44. var  Frame : FrameObj;
  45.      prnarray : array[0..3] of word absolute $40:$08;
  46.      screen : array[0..65520] of byte absolute $A000:0;
  47.  
  48.      crtmode : byte absolute $40:$49;
  49.      oldmode : byte;
  50.      i:word;
  51.      ib:byte;
  52.      cx:char;
  53.      mf:file;
  54.      filenum:word;
  55.      showframe : boolean;
  56.      fns:string;
  57.      MovieEnabled:boolean;
  58.  
  59.  
  60. {-----------------------------------------------------------}
  61. {     gray level interpretation chart                       }
  62. {                                                           }
  63. {          frame data                                       }
  64. {gray    F3  F2  F1  F0   F3 = frame 3, F2 = frame 2        }
  65. {level:  76  54  32  10   F1 = frame 1, F0 = frame 0        }
  66. {   12:  11  xx  xx  xx   each group of two bits            }
  67. {   11: <11  11  xx  xx   represent the video level         }
  68. {   10: <11 <11  11  xx   for the frame indicated           }
  69. {    9: <11 <11 <11  11                                     }
  70. {    8:  10 <11 <11 <11   xx = any bit pattern              }
  71. {    7: <10  10 <11 <11   <11 = less than 11; (10, 01, 00)  }
  72. {    6: <10 <10  10 <11   <10 = less than 10; (01 or 00)    }
  73. {    5: <10 <10 <10  10   11, 10, 01, or 00 = the indicated }
  74. {    4:  01 <10 <10 <10                absolute bit pattern }
  75. {    3:  00  01 <10 <10                                     }
  76. {    2:  00  00  01 <10   the gray level for the specified  }
  77. {    1:  00  00  00  01   bit pattern is shown at the left  }
  78. {    0:  00  00  00  00                                     }
  79. {-----------------------------------------------------------}
  80. {this array is used to translate from the interpretation    }
  81. {array data into a gray level for display on the screen     }
  82. const IntrpXlat : array[0..255] of byte = (
  83.     0,1,5,9,2,2,5,9,         6,6,6,9,10,10,10,10,
  84.     3,3,5,9,3,3,5,9,         6,6,6,9,10,10,10,10,
  85.     7,7,7,9,7,7,7,9,         7,7,7,9,10,10,10,10,
  86.     11,11,11,11,11,11,11,11, 11,11,11,11,11,11,11,11,
  87.     4,4,5,9,4,4,5,9,         6,6,6,9,10,10,10,10,
  88.     4,4,5,9,4,4,5,9,         6,6,6,9,10,10,10,10,
  89.     7,7,7,9,7,7,7,9,         7,7,7,9,10,10,10,10,
  90.     11,11,11,11,11,11,11,11, 11,11,11,11,11,11,11,11,
  91.     8,8,8,9,8,8,8,9,         8,8,8,9,10,10,10,10,
  92.     8,8,8,9,8,8,8,9,         8,8,8,9,10,10,10,10,
  93.     8,8,8,9,8,8,8,9,         8,8,8,9,10,10,10,10,
  94.     11,11,11,11,11,11,11,11, 11,11,11,11,11,11,11,11,
  95.     12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,
  96.     12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,
  97.     12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,
  98.     12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12);
  99.  
  100. {-----------------------------------------------------------}
  101.  
  102.  
  103.     {grab a chunk of video from inprt size bytes in length into fary}
  104.     function FrameObj.GrabFrame(inprt,size:word; Fptr:frameptr):boolean; assembler;
  105.      asm
  106.       mov bx,17000      {timeout if we go over 50ms without sync}
  107.       mov dx,[inprt]
  108.       les di,[Fptr]     {now collect a frame}
  109.       mov cx,0
  110.  
  111.      @vsloop1:
  112.       mov ah,8 {[vsyncslice]}  {if we are in a vert sync, get out of it first}
  113.      @vsloop2:
  114.       dec bx
  115.       jz @vdone
  116.       in al,dx
  117.       shl al,1
  118.       jc @vsloop1
  119.       dec ah
  120.       jnz @vsloop2
  121.  
  122.      @vsloop3:
  123.       mov ah,8 {[vsyncslice]}  {find the start of a vert sync}
  124.      @vsloop4:
  125.       dec bx
  126.       jz @vdone
  127.       in al,dx
  128.       shl al,1
  129.       jnc @vsloop3
  130.       dec ah
  131.       jnz @vsloop4
  132.  
  133.       cld
  134.       mov cx,[size]      {start collecting data}
  135.       rep
  136.       db 6ch
  137.  
  138.      @vdone:
  139.       xor al,al        {return error code}
  140.       or bh,bl         {one = all ok}
  141.       jz @vexit        {zero = no sync}
  142.       inc al
  143.      @vexit:
  144.     end;
  145.  
  146.  
  147.   Constructor FrameObj.Init;
  148.   var i:byte;
  149.   begin
  150.  
  151.     for i := 0 to 3 do
  152.     begin
  153.       new(fary[i]);
  154.       fillchar(fary[i]^,sizeof(fary[i]^),0);
  155.     end;
  156.     new(iary);
  157.     fillchar(iary^,sizeof(iary^),0);
  158.     move(IntrpXlat,iary^,256);
  159.     new(dary);
  160.     fillchar(dary^,sizeof(dary^),0);
  161.     move(IntrpXlat,dary^,256);
  162.   end;
  163.  
  164.  
  165.   Destructor FrameObj.Done;
  166.   var i:byte;
  167.   begin
  168.     for i := 0 to 3 do
  169.     begin
  170.       dispose(fary[i]);
  171.     end;
  172.     dispose(iary);
  173.     dispose(dary);
  174.   end;
  175.  
  176.  
  177.   procedure FrameObj.SetFramePort(what:string8);
  178.   begin
  179.     frameport := 0;
  180.     if length(what) > 0 then
  181.     begin
  182.       case what[1] of
  183.        '2': frameport := 1;
  184.        '3': frameport := 2;
  185.        '4': frameport := 3;
  186.       end;
  187.     end;
  188.     outport := prnarray[frameport]; {- $378}  {get port base addr}
  189.     inport := outport+1;    {- $379}
  190.  
  191.     port[outport+2] := $04; {- $37A}   {init output control lines}
  192.     port[outport] := $ff;    {init data lines}
  193.     grabsize := 20000;           {default grab size}
  194.     framenum := 0;
  195.     IntrpWidth := 70;
  196.     IntrpSize := IntrpWidth*(262-12);
  197.     framecount := 0;
  198.    end;
  199.  
  200.  
  201.  
  202.   function FrameObj.GrabOne:boolean;
  203.   var Fptr : framePtr;
  204.   begin
  205.     inc(framenum);
  206.     framenum := framenum and 3;
  207.     port[frame.outport] := (framenum shl 6) or $3f;
  208.     Fptr := fary[framenum];
  209.     asm CLI; end;
  210.     GrabOne := GrabFrame(inport,grabsize,Fptr);
  211.     asm STI; end;
  212.     port[frame.outport] := $3f;
  213.   end;
  214.  
  215.  
  216.  
  217.    {==================================================================}
  218.    {note: this assumes that the frame grab array has been preformated}
  219.    {with starting with a valid scan line at the top of the screen}
  220.    procedure FrameObj.F2Iconvert(Fnum:byte; GSize,IWidth,ISize:word;
  221.                                  Iptr:IntrpPtr; Fptr:FramePtr);
  222.    var Bottom:word;
  223.    begin
  224.      asm
  225.        cld
  226.        mov cl,ss:[Fnum]      {get gray scale frame number}
  227.        and cl,03H
  228.        add cl,cl             {*2 = shifter count}
  229.        mov ch,0FCH           {create intrp data mask}
  230.        rol ch,cl
  231.        mov dx,ss:[GSize]     {get size of grabbed data to convert}
  232.        inc dx
  233.        les di,ss:[Iptr]      {get intrp array pointer}
  234.        add di,256            {first 256 bytes has xlat array}
  235.        mov ax,di
  236.        add ax,ss:[ISize]     {compute intrp bottom address offset}
  237.        mov ss:[Bottom],ax    {and save it}
  238.        mov bx,ss:[IWidth]    {put intrp right edge offset into bx}
  239.  
  240.        push ds               {save current data segment}
  241.        lds si,ss:[Fptr]      {get video frame pointer to DS:SI}
  242.        add si,500            {ignore the vertical sync}
  243.  
  244.      {data conversion loop starts here}
  245.      @loop1:
  246.        dec dx           {did we run out of data?}
  247.        jz @done
  248.        lodsb            {get a frame scan byte}
  249.        shl al,1         {if it is a sync, try again}
  250.        jc @loop1
  251.  
  252.      @loop2:
  253.        dec dx           {did we run out of data?}
  254.        jz @done
  255.        lodsb            {get a frame scan byte}
  256.        shl al,1         {if it is a sync, we are}
  257.        jc @loop4        {done with the scan line}
  258.  
  259.        {convert scan input data to intrp level reference}
  260.        xor ah,ah        {init to zero level}
  261.        shl al,1         {if highest level on}
  262.        adc ah,0         {add one to level count}
  263.        shl al,1         {if next high level on}
  264.        adc ah,0         {add one to level count}
  265.        shl al,1         {if lowest level on}
  266.        adc ah,0         {add one to level count}
  267.        shl ah,cl        {adjust result to position}
  268.        mov al,es:[di]   {get current intrp value}
  269.        and al,ch        {strip old intrp value}
  270.        or al,ah         {insert new intrp value}
  271.        mov es:[di],al   {save the new intrp value}
  272.        inc di
  273.        dec bx           {if not at end of intrp line}
  274.        jnz @loop2       {go process the next byte}
  275.  
  276.      {ran against right edge of intrp window}
  277.      {so throw away rest of the scan data}
  278.      @loop3:            {suck up extra scan data}
  279.        dec dx           {did we run out of data?}
  280.        jz @done
  281.        lodsb            {get a frame scan byte}
  282.        shl al,1         {if it is not a sync, }
  283.        jnc @loop3       {keep looping}
  284.        jmp @loopd
  285.  
  286.      @loop4:            {fill out rest of intrp data}
  287.        and es:[di],ch   {strip old intrp value to 0}
  288.        inc di
  289.        dec bx           {loop until right edge reached}
  290.        jnz @loop4
  291.  
  292.      @loopd:
  293.        mov bx,ss:[IWidth]    {restore width to reg BX}
  294.        cmp di,ss:[Bottom]    {are we at bottom?}
  295.        jc @loop1             {do more if not at bottom}
  296.  
  297.      @done:
  298.        pop ds             {restore DS and we are done}
  299.      end;
  300.    end;
  301.  
  302.  
  303. {=====================================================================}
  304.    {now we are gonna display the video on the screen}
  305.    procedure FrameObj.IntrpDisplay(fnum,IWidth,ISize:word; Iptr:IntrpPtr);
  306.    var Bottom:word;
  307.    begin
  308.      asm
  309.        cld
  310.        push ds
  311.        lds si,ss:[Iptr]      {get intrp array pointer}
  312.        mov bx,si             {point bx at the start of the array}
  313.        add si,256            {first 256 bytes has intpr array}
  314.        mov ax,ss:[ISize]     {compute intrp bottom address offset}
  315.        add ax,si
  316.        mov ss:[Bottom],ax    {and save it}
  317.        mov ax,0A000h         {point es to the display segment}
  318.        mov es,ax
  319.        mov cx,ss:[IWidth]    {put intrp right edge offset}
  320.        mov di,fnum           {start at top left corner of screen}
  321.        and di,1              {offset by frame number count (even/odd)}
  322.        jz @dlp1
  323.        add si,cx             {use odd scan lines on odd video frames}
  324.  
  325.      @dlp1:
  326.        push di
  327.      @dlp2:
  328.        lodsb          {get a intrp byte}
  329.  
  330.        xlat           {translate it to gray scale number}
  331.        stosb          {display it}
  332.        inc di         {skip a display pixel (we get it next time)}
  333.        dec cx         {end of the scan line?}
  334.        jnz @dlp2      {loop until done}
  335.        pop di         {restore original display start offset}
  336.        add di,320     {add display width to it}
  337.        mov cx,ss:[IWidth]  {restore Iwidth to cx}
  338.        add si,cx
  339.        add si,cx           {skip three video scan lines}
  340.        add si,cx
  341.        cmp si,ss:[Bottom]  {are we at the bottom?}
  342.        jc @dlp1            {keep going if not}
  343.  
  344.      @done:
  345.        pop ds          {ok, we're done}
  346.      end;
  347.    end;
  348.  
  349.  
  350. {=====================================================================}
  351.    {now we are gonna display the video on the screen}
  352.    procedure FrameObj.MakeDiskArray(fnum,IWidth,ISize:word;
  353.                                     Iptr:IntrpPtr; Dptr:IntrpPtr);
  354.    var Bottom:word;
  355.    begin
  356.      asm
  357.        cld
  358.        push ds
  359.        lds si,ss:[Iptr]      {get intrp array pointer}
  360.        mov bx,si             {point bx at the start of the array}
  361.        add si,256            {first 256 bytes has intpr array}
  362.        mov ax,ss:[ISize]     {compute intrp bottom address offset}
  363.        add ax,si
  364.        mov ss:[Bottom],ax    {and save it}
  365.        les di,Dptr           {point es:di at the disk array}
  366.        mov cx,ss:[IWidth]    {put intrp right edge offset}
  367.        mov dx,si
  368.        add dx,cx
  369.  
  370.      @dlp1:
  371.        lodsb          {get a intrp byte}
  372.        xlat           {translate it to gray scale number}
  373.        mov ah,al
  374.        xchg si,dx
  375.        lodsb
  376.        xlat
  377.        xchg ah,al
  378.        xchg si,dx
  379.        stosw          {save it in the array}
  380.        dec cx         {end of the scan line?}
  381.        jnz @dlp1      {loop until done}
  382.  
  383.      @dlp3:
  384.        mov cx,ss:[IWidth]  {restore Iwidth to cx}
  385.        add si,cx
  386.        add si,cx           {skip three video scan lines}
  387.        add si,cx
  388.        mov dx,si
  389.        add dx,cx
  390.        cmp si,ss:[Bottom]  {are we at the bottom?}
  391.        jc @dlp1            {keep going if not}
  392.  
  393.      @done:
  394.        pop ds          {ok, we're done}
  395.      end;
  396.    end;
  397.  
  398.  
  399. procedure DisplayMovieFrame(DWidth,DSize:word; Dptr:IntrpPtr);
  400. var Bottom:word;
  401. begin
  402.      asm
  403.        cld
  404.        push ds
  405.        lds si,ss:[Dptr]      {get intrp array pointer}
  406.        mov ax,ss:[DSize]     {compute intrp bottom address offset}
  407.        add ax,si
  408.        mov ss:[Bottom],ax    {and save it}
  409.        mov ax,0A000h         {point es to the display segment}
  410.        mov es,ax
  411.        mov di,0
  412.        mov cx,ss:[DWidth]    {put intrp right edge offset}
  413.  
  414.      @dlp1:
  415.        push di
  416.        rep movsb      {get a movie byte and display it}
  417.        pop di         {restore original display start offset}
  418.        add di,320     {add display width to it}
  419.        mov cx,ss:[DWidth]  {restore Iwidth to cx}
  420.        cmp si,ss:[Bottom]  {are we at the bottom?}
  421.        jc @dlp1            {keep going if not}
  422.  
  423.      @done:
  424.        pop ds          {ok, we're done}
  425.      end;
  426. end;
  427.  
  428. {================================================================}
  429. function fstr(W:word):string8;
  430. var s:string8;
  431. begin
  432.   str(W,S);
  433.   fstr := S;
  434. end;
  435.  
  436.  
  437. {------------------------------------------------------------}
  438. {format of disk file is:                                     }
  439. {       number of frames : word                              }
  440. {    frame size in bytes : word                              }
  441. {   frame width in bytes : word                              }
  442. {       video frame data : array[0..frames] of dary^         }
  443. {------------------------------------------------------------}
  444. procedure OpenMovie;
  445. var MovieWidth : word;
  446.     MovieSize  : word;
  447.     MovieCount : word;
  448. begin
  449.   Frame.FrameCount := 0;
  450.   if Frame.filenum > 9 then frame.filenum := 0;
  451.   MovieWidth := Frame.IntrpWidth*2;
  452.   MovieSize := (Frame.IntrpSize*2) div 3;
  453.   MovieCount := Frame.Framecount;
  454.   fns := 'DCFG'+fstr(Frame.filenum)+'.MOV';
  455.   Assign(mf,fns);
  456.   inc(Frame.filenum);
  457.   rewrite(mf,1);
  458.   blockwrite(mf,MovieCount,2);
  459.   blockwrite(mf,MovieSize,2);
  460.   blockwrite(mf,MovieWidth,2);
  461. end;
  462. procedure WriteMovie;
  463. var MovieSize:word;
  464. begin
  465.   MovieSize := (Frame.IntrpSize*2) div 3;
  466.   inc(frame.framecount);
  467.   Frame.MakeDiskArray(Frame.framenum,Frame.IntrpWidth,
  468.                       Frame.IntrpSize, Frame.Iary, Frame.Dary);
  469.   blockwrite(mf,Frame.Dary^,MovieSize);
  470. end;
  471. procedure CloseMovie;
  472. begin
  473.   reset(mf,1);
  474.   dec(Frame.FrameCount);
  475.   blockwrite(mf,Frame.framecount,2);
  476.   close(mf);
  477. end;
  478.  
  479. procedure ShowMovie(what:char; Rep:boolean);
  480. var MovieWidth:word;
  481.     MovieSize:word;
  482.     MovieCount:word;
  483.     done:boolean;
  484. begin
  485.   showframe := false;
  486.   done := false;
  487.   While not(done) do
  488.   begin
  489.     if not(Rep) then fns := 'DCFG'+what+'.MOV';
  490.     Assign(mf,fns);
  491.     reset(mf,1);
  492.     blockread(mf,MovieCount,2);
  493.     blockread(mf,MovieSize,2);
  494.     blockread(mf,MovieWidth,2);
  495.     inc(MovieCount);
  496.     i := 0;
  497.     while i < MovieCount do
  498.     begin
  499.       blockread(mf,Frame.Dary^,MovieSize);
  500.       DisplayMovieFrame(MovieWidth,MovieSize,Frame.Dary);
  501.       if keypressed then i := MovieCount;
  502.       gotoxy(1,24);
  503.       write('Showing Movie:',fns,' Frame:',i,' ');
  504.       inc(i);
  505.       delay(50);
  506.     end;
  507.     close(mf);
  508.     if not(Rep) then done := true;
  509.     if keypressed then done := true;
  510.   end;
  511.   gotoxy(1,24);
  512.   write('                                  ');
  513. end;
  514.  
  515.  
  516. procedure SaveToFrame;
  517. begin
  518.   OpenMovie;
  519.   WriteMovie;
  520.   CloseMovie;
  521. end;
  522.  
  523.  
  524. { ************************************************************** }
  525. { program start }
  526.  
  527. begin
  528.    writeln;
  529.    cx := #255;
  530.    filenum := 0;
  531.    showframe := true;
  532.    MovieEnabled := false;
  533.  
  534.    directvideo := false;
  535.  
  536.    OldMode := CrtMode;
  537.    asm
  538.      mov ax,$0013    {switch to vga graphics mode}
  539.      mov bx,0
  540.      int $10
  541.    end;
  542.  
  543.    ib := 0;
  544.    while ib < 15 do    {load palettes with gray levels}
  545.    begin
  546.      asm
  547.        mov ax,1010h
  548.        mov ch,[ib]      {green}
  549.        add ch,ch
  550.        add ch,ch
  551.        mov cl,ch      {blue}
  552.        mov dh,ch      {red}
  553.        mov bl,[ib]
  554.        mov bh,0
  555.        int 10h
  556.      end;
  557.      inc(ib);
  558.    end;
  559.  
  560.    fillchar(screen,sizeof(screen),0);
  561.  
  562.  
  563.    Frame.Init;
  564.    if ParamCount > 0 then
  565.      Frame.SetFramePort(ParamStr(1))
  566.    else
  567.      Frame.SetFramePort('1');
  568.  
  569.    gotoxy(1,20);
  570.    write('X:',Frame.IntrpWidth * 2,' Y:',Frame.IntrpSize div (Frame.Intrpwidth *2),'   ');
  571.  
  572.  repeat
  573.    if Frame.GrabOne then
  574.    begin
  575.       Frame.F2Iconvert(Frame.Framenum,Frame.GrabSize,
  576.                        Frame.IntrpWidth,Frame.IntrpSize,
  577.                        Frame.Iary, Frame.Fary[Frame.framenum]);
  578.  
  579.      if MovieEnabled then
  580.      begin
  581.        WriteMovie;
  582.        gotoxy(1,24);
  583.        write('Movie:',fns,' Frame:',Frame.framecount,'  ');
  584.        gotoxy(1,25);
  585.        write('Movie on  ');
  586.      end
  587.      else
  588.      begin
  589.        gotoxy(1,25);
  590.        write('Movie off ');
  591.      end;
  592.      gotoxy(1,22);
  593.      write('          ');
  594.  
  595.    end
  596.    else
  597.    begin
  598.      gotoxy(1,22);
  599.      write('Lost Sync');
  600.    end;
  601.  
  602.    if ShowFrame then
  603.      Frame.IntrpDisplay(Frame.framenum,Frame.IntrpWidth,
  604.                         Frame.IntrpSize,Frame.Iary);
  605.  
  606.  
  607.    if keypressed then   {key pressed? If so, process it}
  608.         begin
  609.           cx := readkey;
  610.           if cx = #0 then cx := char($80+ord(readkey));
  611.           if MovieEnabled then
  612.           begin
  613.             MovieEnabled := false;
  614.             CloseMovie;
  615.           end;
  616.  
  617.                if upcase(cx) = 'F'then SaveToFrame
  618.           else if upcase(cx) = 'M' then begin OpenMovie; MovieEnabled := true; end
  619.           else if upcase(cx) = 'R' then ShowMovie(cx,true)
  620.           else if upcase(cx) = 'S' then Showframe := false
  621.           else if (cx >= '0') and (cx <= '9') then ShowMovie(cx,false)
  622.           else showframe := true;
  623.  
  624.           gotoxy(1,20);
  625.           write('X:',Frame.IntrpWidth * 2,' Y:',Frame.IntrpSize div (Frame.Intrpwidth *2),'   ');
  626.         end;
  627.  
  628.    until cx < #32;
  629.  
  630.    asm
  631.      mov ah,$00        {restore original display mode}
  632.      mov al,[oldmode]
  633.      mov bx,0
  634.      int $10
  635.    end;
  636.  
  637. end.
  638.