SoundStream.st
author claus
Mon, 06 Feb 1995 00:39:14 +0100
changeset 51 24f978f1d849
parent 45 6768d3213025
child 58 bd6753bf0401
permissions -rw-r--r--
*** empty log message ***

"
 COPYRIGHT (c) 1993 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

FileStream subclass:#SoundStream
	 instanceVariableNames:'sampleRate numberOfChannels bitsPerSample'
	 classVariableNames:'RampOff'
	 poolDictionaries:''
	 category:'Streams-External'
!

SoundStream comment:'
COPYRIGHT (c) 1993 by Claus Gittinger
	      All Rights Reserved

$Header: /cvs/stx/stx/libbasic2/SoundStream.st,v 1.12 1995-02-05 23:38:40 claus Exp $
'!

!SoundStream class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1993 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

version
"
$Header: /cvs/stx/stx/libbasic2/SoundStream.st,v 1.12 1995-02-05 23:38:40 claus Exp $
"
!

documentation
"
    Interface to audio device.
    Currently works with PD sound-blaster driver and
    sun audio device. On iris default setup is 8 bit mono
    so I can play the standard sound files I have here.
    It needs much more work, for stereo, different sampling rates etc.

    see companion classes VocBrowser and VocView in fileIn-directory for
    how to use this class.
"
! !

!SoundStream primitiveDefinitions!

%{
#ifdef IRIS
# ifndef IRIX5
#  define IRIS_AUDIO
# endif
#endif

#ifdef IRIS_AUDIO
# include <audio.h>
# define MKAUDIO(o)  (ALport)(_intVal(o))
#endif
%}
! !

!SoundStream class methodsFor:'instance creation'!

reading
    |newStream|
    newStream := (self basicNew) initialize.
    newStream openForReading isNil ifTrue:[^nil].
    ^ newStream

    "SoundStream reading"
!

writing
    |newStream|

    OperatingSystem getCPUType = '386' ifTrue:[
	"soundblaster special kludge to avoid click"
	RampOff isNil ifTrue:[
	    self generateRamp
	]
    ].
    newStream := (self basicNew) initialize.
    newStream openForWriting isNil ifTrue:[^nil].
    ^ newStream

    "SoundStream writing"
!

writing16BitStero
    "just an example, has never been tried (I also
     have no samples for this ... leave it as an exercise"

    |newStream|

    OperatingSystem getCPUType = 'irix' ifFalse:[
	self error:'unsupported audio mode'.
	^ nil
    ].
    newStream := (self basicNew) initialize.
    newStream bitsPerSample:16.
    newStream numberOfChannels:2.
    newStream openForWriting isNil ifTrue:[^nil].
    ^ newStream

    "SoundStream writing16BitStereo"
! !

!SoundStream class methodsFor:'default values'!

defaultSampleRate
    "minimum, supported by all audio systems"

    ^ 8000
!

defaultBitsPerSample
    "minimum, supported by all audio systems"

    ^ 8
!

defaultNumberOfChannels
    "minimum, supported by all audio systems"

    ^ 1
!

defaultMode
    ^ #linear "planned is at least uLaw"
!

blockSize
    "a good chunk size to read soundstream.
     Some devices may force a specific size ..."

    ^ 2048 "about 1/4 of a second" 
! !

!SoundStream class methodsFor:'conversion tables'!

uLawToLinear:uLawValue
    "currently unused - but will be"

    ^ #(
	"0  "     -32256
	"1  "     -31228
	"2  "     -30200
	"3  "     -29172
	"4  "     -28143
	"5  "     -27115
	"6  "     -26087
	"7  "     -25059
	"8  "     -24031
	"9  "     -23002
	"10 "     -21974
	"11 "     -20946
	"12 "     -19918
	"13 "     -18889
	"14 "     -17861
	"15 "     -16833
	"16 "     -16062
	"17 "     -15548
	"18 "     -15033
	"19 "     -14519
	"20 "     -14005
	"21 "     -13491
	"22 "     -12977
	"23 "     -12463
	"24 "     -11949
	"25 "     -11435
	"26 "     -10920
	"27 "     -10406
	"28 "     -9892
	"29 "     -9378
	"30 "     -8864
	"31 "     -8350
	"32 "     -7964
	"33 "     -7707
	"34 "     -7450
	"35 "     -7193
	"36 "     -6936
	"37 "     -6679
	"38 "     -6422
	"39 "     -6165
	"40 "     -5908
	"41 "     -5651
	"42 "     -5394
	"43 "     -5137
	"44 "     -4880
	"45 "     -4623
	"46 "     -4365
	"47 "     -4108
	"48 "     -3916
	"49 "     -3787
	"50 "     -3659
	"51 "     -3530
	"52 "     -3402
	"53 "     -3273
	"54 "     -3144
	"55 "     -3016
	"56 "     -2887
	"57 "     -2759
	"58 "     -2630
	"59 "     -2502
	"60 "     -2373
	"61 "     -2245
	"62 "     -2116
	"63 "     -1988
	"64 "     -1891
	"65 "     -1827
	"66 "     -1763
	"67 "     -1698
	"68 "     -1634
	"69 "     -1570
	"70 "     -1506
	"71 "     -1441
	"72 "     -1377
	"73 "     -1313
	"74 "     -1249
	"75 "     -1184
	"76 "     -1120
	"77 "     -1056
	"78 "     -992
	"79 "     -927
	"80 "     -879
	"81 "     -847
	"82 "     -815
	"83 "     -783
	"84 "     -751
	"85 "     -718
	"86 "     -686
	"87 "     -654
	"88 "     -622
	"89 "     -590
	"90 "     -558
	"91 "     -526
	"92 "     -494
	"93 "     -461
	"94 "     -429
	"95 "     -397
	"96 "     -373
	"97 "     -357
	"98 "     -341
	"99 "     -325
	"100"     -309
	"101"     -293
	"102"     -277
	"103"     -261
	"104"     -245
	"105"     -228
	"106"     -212
	"107"     -196
	"108"     -180
	"109"     -164
	"110"     -148
	"111"     -132
	"112"     -120
	"113"     -112
	"114"     -104
	"115"     -96
	"116"     -88
	"117"     -80
	"118"     -72
	"119"     -64
	"120"     -56
	"121"     -48
	"122"     -40
	"123"     -32
	"124"     -24
	"125"     -16
	"126"     -8
	"127"     0
	"128"     32256
	"129"     31228
	"130"     30200
	"131"     29172
	"132"     28143
	"133"     27115
	"134"     26087
	"135"     25059
	"136"     24031
	"137"     23002
	"138"     21974
	"139"     20946
	"140"     19918
	"141"     18889
	"142"     17861
	"143"     16833
	"144"     16062
	"145"     15548
	"146"     15033
	"147"     14519
	"148"     14005
	"149"     13491
	"150"     12977
	"151"     12463
	"152"     11949
	"153"     11435
	"154"     10920
	"155"     10406
	"156"     9892
	"157"     9378
	"158"     8864
	"159"     8350
	"160"     7964
	"161"     7707
	"162"     7450
	"163"     7193
	"164"     6936
	"165"     6679
	"166"     6422
	"167"     6165
	"168"     5908
	"169"     5651
	"170"     5394
	"171"     5137
	"172"     4880
	"173"     4623
	"174"     4365
	"175"     4108
	"176"     3916
	"177"     3787
	"178"     3659
	"179"     3530
	"180"     3402
	"181"     3273
	"182"     3144
	"183"     3016
	"184"     2887
	"185"     2759
	"186"     2630
	"187"     2502
	"188"     2373
	"189"     2245
	"190"     2116
	"191"     1988
	"192"     1891
	"193"     1827
	"194"     1763
	"195"     1698
	"196"     1634
	"197"     1570
	"198"     1506
	"199"     1441
	"200"     1377
	"201"     1313
	"202"     1249
	"203"     1184
	"204"     1120
	"205"     1056
	"206"     992
	"207"     927
	"208"     879
	"209"     847
	"210"     815
	"211"     783
	"212"     751
	"213"     718
	"214"     686
	"215"     654
	"216"     622
	"217"     590
	"218"     558
	"219"     526
	"220"     494
	"221"     461
	"222"     429
	"223"     397
	"224"     373
	"225"     357
	"226"     341
	"227"     325
	"228"     309
	"229"     293
	"230"     277
	"231"     261
	"232"     245
	"233"     228
	"234"     212
	"235"     196
	"236"     180
	"237"     164
	"238"     148
	"239"     132
	"240"     120
	"241"     112
	"242"     104
	"243"     96
	"244"     88
	"245"     80
	"246"     72
	"247"     64
	"248"     56
	"249"     48
	"250"     40
	"251"     32
	"252"     24
	"253"     16
	"254"     8
	"255"     0
    ) at:(uLawValue + 1)
! !

!SoundStream class methodsFor:'private'!

generateRamp
    "create ramp data (need to be played on soundblaster
     before closing device to prevent audible click-on/off).
     This method just fills a buffer with ramp data."

    |size div|

    size := 256.
    div := size // 128.
    RampOff := ByteArray new:size.
    1 to:size do:[:index |
       RampOff at:index put:(size - index // div)
    ]

    "SoundStream generateRamp"
! !

!SoundStream methodsFor:'mode setting'!

sampleRate:aNumber
    "set the sample rate in hertz - on some
     devices, this is a nop"

    OperatingSystem getCPUType = '386' ifTrue:[
	"assume sound-blaster device"
	"set sound blasters sample rate"

	sampleRate := aNumber.
	self ioctl:1 "DSP_IOCTL_SPEED" with:aNumber
    ].
    OperatingSystem getOSType = 'sunos' ifTrue:[
	"audio sample rate is fix"
	^ self "cannot change"
    ].
    OperatingSystem getOSType = 'irix' ifTrue:[
	"could change using ALibrary ...for now, it is fix"
	sampleRate := aNumber.
    ].
!

sampleRate
    "return the sample rate"

    ^ sampleRate
!

numberOfChannels
    "return the number of channels (1 or 2; usually 1)"

    ^ numberOfChannels
!

numberOfChannels:aNumber
    "set the number of channels"

    numberOfChannels := aNumber
!

bitsPerSample
    "return the number of bits per sample - usually 8"

    ^ bitsPerSample
!

bitsPerSample:aNumber
    "set the number of bits per sample"

    bitsPerSample := aNumber
! !

!SoundStream methodsFor:'catching invalid methods'!

pathName:filename
    "catch pathname access - its fixed here"

    self shouldNotImplement
!

pathName:filename in:aDirectory
    "catch pathname access - its fixed here"

    self shouldNotImplement
! !

!SoundStream methodsFor:'private'!

initialize 
    "initialize for least common mode"

    buffered := false.
    bitsPerSample := 8.
    numberOfChannels := 1.
    sampleRate := 8000.

    OperatingSystem getOSType = 'sunos' ifTrue:[
	pathName := '/dev/audio'.
    ].

    OperatingSystem getOSType = 'irix' ifTrue:[
	"no device, use special library calls"
	pathName := nil.
    ].

    OperatingSystem getCPUType = '386' ifTrue:[
	"this code assumes a PD sound-blaster driver .."
	pathName := '/dev/sbdsp'
    ]
! !

!SoundStream methodsFor:'redefined'!

openWithMode:aMode
    OperatingSystem getOSType = 'irix' ifFalse:[
	"its a regular file open"
	^ super openWithMode:aMode
    ].

    ((aMode = 'r') or:[aMode = 'w']) ifFalse:[
	self error:'invalid mode'.
	^ nil
    ].
%{
#ifdef IRIS_AUDIO
  {
    ALconfig config;
    ALport p;
    long params[] = {
	AL_INPUT_SOURCE, AL_INPUT_MIC,
	AL_INPUT_RATE, 8000,
	AL_OUTPUT_RATE, 8000,
    };

    config = ALnewconfig();
    if (_INST(numberOfChannels) == _MKSMALLINT(2))
	ALsetchannels(config, AL_STEREO);
    else
	ALsetchannels(config, AL_MONO);
    if (_INST(bitsPerSample) == _MKSMALLINT(16))
	ALsetwidth(config, AL_SAMPLE_16);
    else
	ALsetwidth(config, AL_SAMPLE_8);

    if (_isSmallInteger(_INST(sampleRate)))
	params[3] = params[5] = _intVal(_INST(sampleRate));

    ALsetparams(AL_DEFAULT_DEVICE, params, 6);
    p = ALopenport("smallchat", (char *)_stringVal(aMode), config);
    if (p) {
	_INST(filePointer) = MKOBJ(p);
    } else {
	_INST(filePointer) = nil;
	RETURN (nil);
    }
    _INST(binary) = true;

    ALfreeconfig(config);
    /*
     * get the parameters actually installed
     */
    config = ALgetconfig(p);
    switch (ALgetchannels(config)) {
	default:
	    /* cannot happen */
	case AL_MONO:
	    _INST(numberOfChannels) = _MKSMALLINT(1);
	    break;
	case AL_STEREO:
	    _INST(numberOfChannels) = _MKSMALLINT(2);
	    break;
    }
    switch (ALgetwidth(config)) {
	default:
	    /* cannot happen */
	case AL_SAMPLE_8:
	    _INST(bitsPerSample) = _MKSMALLINT(8);
	    break;
	case AL_SAMPLE_16:
	    _INST(bitsPerSample) = _MKSMALLINT(16);
	    break;
	case AL_SAMPLE_24:
	    _INST(bitsPerSample) = _MKSMALLINT(24);
	    break;
    }
    ALgetparams(AL_DEFAULT_DEVICE, params, 6);
    _INST(sampleRate) = _MKSMALLINT(params[3]);

    ALfreeconfig(config);
  }
#endif
%}
.
    ^ self
!

close
    OperatingSystem getOSType = 'irix' ifTrue:[
	^ self closeFile
    ].

    (mode == #writeonly) ifTrue:[
	"special handling of close on sound blaster,
	 turn off voice but continue playing a ramp
	 to avoid audible click"

	OperatingSystem getCPUType = '386' ifTrue:[
	    "assume sound-blaster device"
	    self ioctl:2 "DSP_IOCTL_VOICE" with:0.

	    "OperatingSystem sleep:3.      "
	    "add a ramp to zero to prevent click-off"
	    super nextPutBytes:(RampOff size) from:RampOff
	]
    ].    
    super close
!

nextPutBytes:count from:anObject startingAt:start
    "write count bytes from an object starting at index start.
     return the number of bytes written or nil on error.
     Redefined, since IRIS audio library cannot be used with stdio.
     (at least I dont know). Use with ByteArrays only."

%{
#ifdef IRIS_AUDIO
  {
    ALport p;
    int cnt, offs;
    int objSize;
    char *cp;

    if (_INST(filePointer) != nil) {
	if (_INST(mode) != _readonly) {
	    if (_isSmallInteger(count) && _isSmallInteger(start)) {
		cnt = _intVal(count);
		offs = _intVal(start) - 1;
		p = MKAUDIO(_INST(filePointer));

		/*
		 * compute number of samples
		 */
		objSize = _Size(anObject) - OHDR_SIZE;
		if ( (offs >= 0) && (cnt >= 0) && (objSize >= (cnt + offs)) ) {
		    cp = (char *)_InstPtr(anObject) + OHDR_SIZE + offs;
		    if (_INST(bitsPerSample) == _MKSMALLINT(16))
			ALwritesamps(p, cp, cnt / 2);
		    else
			ALwritesamps(p, cp, cnt);
		    RETURN ( _MKSMALLINT(cnt) );
		}
	    }
	}
    }
  }
#endif
%}
.
    OperatingSystem getOSType = 'irix' ifFalse:[
	^ super nextPutBytes:count from:anObject startingAt:start
    ].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #readonly) ifTrue:[^ self errorReadOnly].
    self primitiveFailed
!

nextBytes:count into:anObject startingAt:start
    "read the next count bytes into an object and return the number of
     bytes read or nil on error.
     Use with ByteArrays only."

%{
#ifdef IRIS_AUDIO
  {
    ALport p;
    int cnt, offs;
    int objSize;
    char *cp;

    if (_INST(filePointer) != nil) {
	if (_INST(mode) != _writeonly) {
	    if (_isSmallInteger(count) && _isSmallInteger(start)) {
		cnt = _intVal(count);
		offs = _intVal(start) - 1;
		p = MKAUDIO(_INST(filePointer));
		objSize = _Size(anObject) - OHDR_SIZE;
		if ((offs >= 0) && (cnt >= 0) && (objSize >= (cnt + offs))) {
		    cp = (char *)_InstPtr(anObject) + OHDR_SIZE + offs;
		    if (_INST(bitsPerSample) == _MKSMALLINT(16))
			ALreadsamps(p, cp, cnt / 2);
		    else
			ALreadsamps(p, cp, cnt);
		    RETURN ( _MKSMALLINT(cnt) );
		}
	    }
	}
    }
  }
#endif
%}
.
    OperatingSystem getOSType = 'irix' ifFalse:[
	^ super nextPutBytes:count from:anObject startingAt:start
    ].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    self primitiveFailed
!

synchronizeOutput
    "wait until all sound has been played"

%{ 
#ifdef IRIS_AUDIO
    ALport p;

    if (_INST(filePointer) != nil) {
	p = MKAUDIO(_INST(filePointer));
	while (ALgetfilled(p) > 0) {
	    sginap(1);
	}
    }
#endif
%}
.
    OperatingSystem getOSType = 'irix' ifFalse:[
	"dont know how to wait"
    ].
    ^ self
! !

!SoundStream methodsFor:'instance release'!

closeFile
    "a stream has been collected - close the file"

    OperatingSystem getOSType = 'irix' ifFalse:[
	^ super closeFile
    ].
%{ 
#ifdef IRIS_AUDIO
    ALcloseport(MKAUDIO(_INST(filePointer)));
#endif
%}
! !

!SoundStream methodsFor:'sine wave generation'!

tuneTone
    |buffer numSamples val scale|

    "allocate memory for 1sec playing time"
    numSamples := self sampleRate.
    buffer := ByteArray new:numSamples.

    "fill it with a sine wave"

    scale := 440 * 2 * (Float pi).
    1 to:numSamples do:[:i |
	val := (scale * i / numSamples) sin.
	val := (val * 127 + 128) rounded.
	buffer at:i put:val
    ].

    buffer inspect.

    1 to:3 do:[:s |
	self nextPutBytes:numSamples from:buffer
    ]

    "SoundStream writing tuneTone"
! !