Welcome back! In my last issue I left off giving some C source code for playing a digital sound under K-Windows. This time, I'm pleased to write that Ted Jaeger has sent me some BASIC source code ("play.bas" -- listing #1) to demonstrate playing .IFF sound files under K-Windows. Thanks, Ted! Play.bas requires the two subroutines "htoi.bas" and "itoh.bas" (listings #2 & #3, respectively) to help determine the IFF sound file header information.
One advantage to using C over BASIC for a 'play' function is memory allocation. Using C, a programmer can dynamically allocate memory to store a sound in memory (see my column in the last issue for an example). With BASIC, you're pretty much stuck with the amount of memory you received at run-time. Worse, under OSk, the user is the one that has to remember to request additional memory, if needed. Ted handles this BASIC shortfall very nicely by using the windowing system to dynamically allocate the memory needed for the sound to reside in. By using K-Window's GetBlk function ($1b $2c) and some strategic SysCall's, "play.bas" defines a get/put buffer, determines its memory address, reads in the digital sound data, starts the sound sample playing , and then releases the buffer memory back to the system.
One interesting note, under MM/1 K-Windows digital sound is controlled by a DMA sound chip which takes care of all the sound output, thus freeing up the system's CPU for other tasks at hand. When Ted's program exits, the sound sample is still probably not finished playing (unless it's extremely short -- as in less than a second in length), yet it's memory is deallocated when the program exits. Will this cause a problem? Not really. The worst thing that can happen under this circumstance would be for another process to allocate the same memory the sound sample is occupying, and write over the sound sample before it's finished playing. In that instance, the sound data would be corrupted, and you would hear garbage for the remaining play duration. In my experience, this is a rare case scenario -- but rest assured you can induce such a occurrence if your heart is set on it!
Listing #1 -- "play.bas" ======================== PROCEDURE play (* subroutine to play brief digitized sounds saved (* in monophonic iff format (* call play passing complete path name of (* the sound file e.g., /dd/snds/ding.iff (* if packed run with new RunB (data size=8000 bytes) (* December 15, 1993 PARAM filename:STRING[20] (* register mask for system calls TYPE registers=d(8),a(8),pc:INTEGER DIM regs:registers DIM callcode:INTEGER (* avoid BGFX by setting up variables (* to PUT graphics commands DIM openbuf(12):BYTE openbuf(1)=$1b openbuf(2)=$2c DIM kilbuf(4):BYTE kilbuf(1)=$1b kilbuf(2)=$2a (* array for first 34 bytes of sound file header (* we'll find length and frequency info here DIM header(34):BYTE (* variables for integer to hex and (* hex to integer conversions DIM x,y,z:BYTE DIM x$,y$,z$:STRING[2] DIM answer:INTEGER DIM answer$:STRING[6] DIM hex:STRING[2] (* sound file buffer data DIM bufsize:INTEGER DIM buflocate:INTEGER DIM pixels:INTEGER (* process id needed so DIM id:INTEGER DIM group:BYTE DIM path:INTEGER ON ERROR GOTO 1000 (* get the process id (* for group number of buffer callcode=$0c RUN syscall(callcode,regs) id=regs.d(1) group=id (* load first 34 bytes of sound file header OPEN #path,filename GET #path,header CLOSE #path (* get the buffer size of sound file x=header(6) y=header(7) z=header(8) RUN itoh(x,hex) x$=hex RUN itoh(y,hex) y$=hex RUN itoh(z,hex) z$=hex answer$=x$+y$+z$ RUN htoi(answer$,answer) bufsize=answer (* if sound file too large abort IF bufsize>53248 THEN END "Sound file too large" ENDIF (* get sampling frequency of sound file x=header(33) y=header(34) RUN itoh(x,hex) x$=hex RUN itoh(y,hex) y$=hex answer$=x$+y$ RUN htoi(answer$,answer) freq=answer (* open a buffer to hold sound file (* size of buffer determined in offset (* 9, 10, 11, 12 of openbuf array (* x dimension of buffer fixed at 512 (* y dimension of buffer varies to allow proper size (* for holding sound file (* size calculated by multiplying # of bytes in buffer X 2 (* this indicates # of needed pixels in buffer (* because in Type 0 screen there are 2 pixels per byte pixels=bufsize*2 (* since 512 pixels per row we can divide total pixels (* by 512 to determine how many rows are needed in the (* open buffer call y=pixels/512 (* cant exceed max number of rows on screen IF y>208 THEN y=208 ENDIF openbuf(3)=group openbuf(4)=1 openbuf(5)=0 openbuf(6)=0 openbuf(7)=0 openbuf(8)=0 openbuf(9)=2 openbuf(10)=0 openbuf(11)=0 openbuf(12)=y PUT #1,openbuf (* map the buffer callcode=$8d regs.d(1)=1 regs.d(2)=$a9 regs.d(3)=group*256+1 RUN syscall(callcode,regs) buflocate=regs.a(1) (* open and read in entire sound file OPEN #path,filename (* I$Read callcode=$89 regs.d(1)=path regs.d(2)=bufsize regs.a(1)=buflocate RUN syscall(callcode,regs) CLOSE #path (* play sound file callcode=$8e regs.d(1)=1 regs.d(2)=$97 regs.d(3)=bufsize regs.d(4)=freq/2 regs.d(5)=0 regs.d(6)=0 regs.d(7)=0 regs.a(1)=buflocate RUN syscall(callcode,regs) (* kill the buffer holding the sound file (* VERY important because sound buffers are (* large and RUNB limits data memory to 8000 bytes kilbuf(3)=group kilbuf(4)=1 PUT #1,kilbuf END 1000 (* report error errnum=ERR PRINT "got error "; errnum Listing #2 -- "htoi.bas" ======================== PROCEDURE htoi (* convert a 6 digit or smaller (* hex value to integer (* hex value received as a string (* in hex$ (* December 15, 1993 PARAM hex$:STRING[6] PARAM answer:INTEGER DIM digit(6):INTEGER DIM l$:STRING[1] DIM l:INTEGER DIM count:INTEGER (* examine digits in hex$ individually FOR count=1 TO LEN(hex$) l$=MID$(hex$,count,1) (* and convert them to integer format IF l$="a" OR l$="A" THEN l=10 ELSE IF l$="b" OR l$="B" THEN l=11 ELSE IF l$="c" OR l$="C" THEN l=12 ELSE IF l$="d" OR l$="D" THEN l=13 ELSE IF l$="e" OR l$="E" THEN l=14 ELSE IF l$="f" OR l$="F" THEN l=15 ELSE l=VAL(l$) ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF (* now get the value of each digit digit(count)=l*16^(LEN(hex$)-count) NEXT count (* now find total of converted hex values answer=0 FOR count=1 TO LEN(hex$) answer=answer+digit(count) NEXT count END Listing #3 -- "itoh.bas" ======================== PROCEDURE itoh (* Convert a value < 256 to hex (* December 15, 1993 PARAM value:BYTE PARAM answer$:STRING[2] DIM x:INTEGER DIM x$:STRING[1] DIM digit1,digit2:STRING[1] (* get digit in 16s place first x=value/16 GOSUB 10 digit1=x$ (* now get digit in 1s place x=MOD(value,16) GOSUB 10 digit2=x$ (* now add the two digits to get hex value answer$=digit1+digit2 END 10 (* get one hex digit of the answer IF x=15 THEN x$="f" ELSE IF x=14 THEN x$="e" ELSE IF x=13 THEN x$="d" ELSE IF x=12 THEN x$="c" ELSE IF x=11 THEN x$="b" ELSE IF x=10 THEN x$="a" ELSE x$=STR$(x) ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF RETURN
Ted has also sent me some source for creating a fancy shell window from within BASIC (listing #4). For those who prefer to program under C, I've ported it for you and included the source (listing #5). The window which is created for the shell is NOT an overlay window, but rather is an independent window, which can be moved, resized, or pushed back. To close up the shell window, just hit the 'Esc' key. Ted stayed away from direct BGFX calls for his BASIC source, so I stayed away from the CGFX library calls in my C version, just in case anyone thought you absolutely _had_ to use CGFX library calls to do K-Windows screen functions.
Listing #4 -- "popup.bas" ========================= PROCEDURE popup (* place a sizeable, relocatable, push-back (* window on the current Type 0 screen (* press ESC key to exit pop up window (* December 16, 1993 (* register mask for system calls TYPE registers=d(8),a(8):INTEGER DIM regs:registers (* variables to make and add features to the new window (* let's avoid forking shells or calling BGFX (* so we'll set up to PUT the commands DIM dwset(9):BYTE DIM frame(3):BYTE DIM move(21):BYTE DIM shape(22):BYTE DIM push(21):BYTE (* variables to dup paths DIM oldpath(3):INTEGER DIM newpath:INTEGER DIM path:INTEGER DIM I_Dup:INTEGER I_Dup=$82 DIM errnum:INTEGER ON ERROR GOTO 100 (* open the path to new window OPEN #newpath,"/w" (* set it up to display on same screen (* with white foreground and green background dwset(1)=$1b dwset(2)=$20 dwset(3)=$ff dwset(4)=$08 dwset(5)=$0a dwset(6)=$40 dwset(7)=$0c dwset(8)=$ff dwset(9)=$04 PUT #newpath,dwset (* give it fancy frame frame(1)=$1e frame(2)=$22 frame(3)=$05 PUT #newpath,frame (* click in upper left corner to move window move(1)=$1e move(2)=$21 move(3)=$00 move(4)=$02 move(5)=0 move(6)=0 move(7)=0 move(8)=0 move(9)=0 move(10)=0 move(11)=0 move(12)=$10 move(13)=0 move(14)=$08 move(15)=0 move(16)=1 move(17)=$80 move(18)=0 move(19)=0 move(20)=0 move(21)=0 PUT #newpath,move (* click in lower right corner to scale window shape(1)=$1e shape(2)=$21 shape(3)=$00 shape(4)=$03 shape(5)=$00 shape(6)=$00 shape(7)=$ff shape(8)=$f0 shape(9)=$ff shape(10)=$f8 shape(11)=$00 shape(12)=$10 shape(13)=$00 shape(14)=$08 shape(15)=$00 shape(16)=$02 shape(17)=$81 shape(18)=$0d shape(19)=0 shape(20)=0 shape(21)=0 shape(22)=0 PUT #newpath,shape (* click in upper right corner to push back window push(1)=$1e push(2)=$21 push(3)=$00 push(4)=$04 push(5)=$00 push(6)=$00 push(7)=$ff push(8)=$f8 push(9)=$00 push(10)=$00 push(11)=$00 push(12)=$08 push(13)=$00 push(14)=$08 push(15)=$00 push(16)=$01 push(17)=$82 push(18)=0 push(19)=0 push(20)=0 push(21)=0 PUT #newpath,push (* select new window PRINT #newpath,CHR$($1b); CHR$($21); (* dup the paths BASE 0 FOR path=0 TO 2 regs.d(0)=path RUN syscall(I_Dup,regs) oldpath(path)=regs.d(0) CLOSE #path regs.d(0)=newpath RUN syscall(I_Dup,regs) NEXT path (* give new window a shell SHELL "" (* return original paths FOR path=0 TO 2 CLOSE #path regs.d(0)=oldpath(path) RUN syscall(I_Dup,regs) CLOSE #oldpath(path) NEXT path (* select original window PRINT #0,CHR$($1b); CHR$($21); CLOSE #newpath END 100 (* report error errnum=ERR PRINT "Got error "; errnum Listing #5 -- "popup.c" ======================= #include <stdio.h> #include <modes.h> #include <errno.h> main() { int save_path[3], wpath, t; /* Open a path to a new window. */ wpath = open("/w", S_IREAD|S_IWRITE); if (wpath == -1) { fprintf(stderr, "Cannot open path to new window!\n"); exit(errno); } /* Set it up on same screen, white on green. */ slow_write(wpath, "\x1b\x20\xff\x08\x0a\x40\x0c\xff\x04", 9); /* Give it a fancy frame. */ write(wpath, "\x1e\x22\x05", 3); /* Click upper-left corner to move window. */ write(wpath, "\x1e\x21\x00\x02\x00\x00\x00\x00\x00\x00\x00", 11); write(wpath, "\x10\x00\x08\x00\x01\x80\x00\x00\x00\x00", 10); /* Click lower-right corner to scale window. */ write(wpath, "\x1e\x21\x00\x03\x00\x00\xff\xf0\xff\xf8\x00", 11); write(wpath, "\x10\x00\x08\x00\x02\x81\x0d\x00\x00\x00\x00", 11); /* Click upper-right corner to push back window. */ write(wpath, "\x1e\x21\x00\x04\x00\x00\xff\xf8\x00\x00\x00", 11); write(wpath, "\x08\x00\x08\x00\x01\x82\x00\x00\x00\x00", 10); /* Select the new window. */ write(wpath, "\x1b\x21", 2); for (t = 0; t <= 2; t++) { save_path[t] = dup(t); close(t); dup(wpath); } system("shell\n"); for (t = 0; t <= 2; t++) { close(t); dup(save_path[t]); close(save_path[t]); } close(wpath); } /* slow_write outputs 'data' one byte at a time. */ /* Needed due to a problem with DWSet codes. */ slow_write(path, data, size) int path; char *data; int size; { int t; char *c; for (t = 0, c = data; t < size; t++, c++) write(path, c, 1); }
Before I logout, I have one correction to make regarding my last column. Colin McKay pointed out that for the listing "playbell.c", an additional INCLUDE statement is needed. After the #include <stdio.h> line, the following line was omitted:
#include <types.h>
My thanks to Colin for pointing this out to me. I've modified my "MACHINE/regs.h" file so it doesn't require the "types.h" header file, which was the cause of the omission.
Best wishes to everyone, and as always, feel free to send in any questions or answers you may have!
-- Joel Mathew Hegberg