"
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"
! !