SoundStream.st
author claus
Sat, 08 Jan 1994 17:23:49 +0100
changeset 11 13c892f36231
parent 6 96ce41566060
child 15 62e2816c87ac
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:'
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.

$Header: /cvs/stx/stx/libbasic2/SoundStream.st,v 1.6 1994-01-08 16:23:49 claus Exp $
'!

%{
#ifdef IRIS
# include <audio.h>
#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
  {
    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) = _MKSMALLINT((int) 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
  {
    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 = (ALport)(_intVal(_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
  {
    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 = (ALport)(_intVal(_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
    ALport p;

    if (_INST(filePointer) != nil) {
        p = (ALport)(_intVal(_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
    ALcloseport( (ALport) (_intVal(_INST(filePointer))) );
#endif
%}
! !

!SoundStream methodsFor:'sine wave generation'!

tuneTone
    |buffer numSamples val|

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

    "fill it with a sine wave"

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

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

    "SoundStream writing tuneTone synchronizeOutput close"
! !