1 /* Synaesthesia - program to display sound graphically
2 Copyright (C) 1997 Paul Francis Harrison
4 This program is free software; you can redistribute it and/or modify it
5 under the terms of the GNU General Public License as published by the
6 Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
9 This program is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License for more details.
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 675 Mass Ave, Cambridge, MA 02139, USA.
18 The author may be contacted at:
19 pfh@yoyo.cc.monash.edu.au
21 27 Bond St., Mt. Waverley, 3149, Melbourne, Australia
26 #include <sys/ioctl.h>
28 #include <sys/resource.h>
29 #include <sys/types.h>
37 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
38 #include <linux/soundcard.h>
39 #include <linux/cdrom.h>
40 //#include <linux/ucdrom.h>
42 #include <sys/soundcard.h>
44 #define CDROM_LEADOUT 0xAA
45 #define CD_FRAMES 75 /* frames per second */
46 #define CDROM_DATA_TRACK 0x4
62 static int trackCount
= 0, *trackFrame
= 0;
64 void cdOpen(char *cdromName
) {
65 //6/7/2000 Some CDs seem to need to be opened nonblocking.
66 //attempt(cdDevice = open(cdromName,O_RDONLY),"talking to CD device",true);
67 attempt(cdDevice
= open(cdromName
,O_RDONLY
|O_NONBLOCK
),"talking to CD device",true);
76 //void cdConfigure(void) {
78 // if (!cdOpen()) return;
79 // int options = (CDO_AUTO_EJECT|CDO_AUTO_CLOSE);
80 // attemptNoDie(ioctl(cdDevice, CDROM_CLEAR_OPTIONS, options),
81 // "configuring CD behaviour");
86 void getTrackInfo(void) {
91 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
92 cdrom_tochdr cdTochdr
;
93 if (-1 == ioctl(cdDevice
, CDROMREADTOCHDR
, &cdTochdr
))
95 ioc_toc_header cdTochdr
;
96 if (-1 == ioctl(cdDevice
, CDIOREADTOCHEADER
, (char *)&cdTochdr
))
99 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
100 trackCount
= cdTochdr
.cdth_trk1
;
102 trackCount
= cdTochdr
.ending_track
- cdTochdr
.starting_track
+ 1;
106 trackFrame
= new int[trackCount
+1];
107 for(i
=trackCount
;i
>=0;i
--) {
108 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
109 cdrom_tocentry cdTocentry
;
110 cdTocentry
.cdte_format
= CDROM_MSF
;
111 cdTocentry
.cdte_track
= (i
== trackCount
? CDROM_LEADOUT
: i
+1);
113 cd_toc_entry cdTocentry
;
114 struct ioc_read_toc_entry t
;
115 t
.address_format
= CD_MSF_FORMAT
;
116 t
.starting_track
= (i
== trackCount
? CDROM_LEADOUT
: i
+1);
117 t
.data_len
= sizeof(struct cd_toc_entry
);
118 t
.data
= &cdTocentry
;
121 //Bug fix: thanks to Ben Gertzfield (9/7/98)
122 //Leadout track is sometimes reported as data.
123 //Added check for this.
124 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
125 if (-1 == ioctl(cdDevice
, CDROMREADTOCENTRY
, & cdTocentry
) ||
126 (i
!= trackCount
&& (cdTocentry
.cdte_ctrl
& CDROM_DATA_TRACK
)))
127 trackFrame
[i
] = (i
==trackCount
?0:trackFrame
[i
+1]);
129 trackFrame
[i
] = cdTocentry
.cdte_addr
.msf
.minute
*60*CD_FRAMES
+
130 cdTocentry
.cdte_addr
.msf
.second
*CD_FRAMES
+
131 cdTocentry
.cdte_addr
.msf
.frame
;
133 if ((ioctl(cdDevice
, CDIOREADTOCENTRYS
, (char *) &t
) == -1) ||
134 (i
!= trackCount
&& (cdTocentry
.control
& CDROM_DATA_TRACK
)))
135 trackFrame
[i
] = (i
==trackCount
?0:trackFrame
[i
+1]);
137 trackFrame
[i
] = cdTocentry
.addr
.msf
.minute
*60*CD_FRAMES
+
138 cdTocentry
.addr
.msf
.second
*CD_FRAMES
+
139 cdTocentry
.addr
.msf
.frame
;
144 int cdGetTrackCount(void) {
147 int cdGetTrackFrame(int track
) {
150 else if (track
<= 1 || track
> trackCount
+1)
151 return trackFrame
[0];
153 return trackFrame
[track
-1];
156 void cdPlay(int frame
, int endFrame
) {
157 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
160 struct ioc_play_msf msf
;
162 if (frame
< 1) frame
= 1;
164 if (!trackFrame
) return;
165 endFrame
= trackFrame
[trackCount
];
168 //Some CDs can't change tracks unless not playing.
172 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
173 msf
.cdmsf_min0
= frame
/ (60*CD_FRAMES
);
174 msf
.cdmsf_sec0
= frame
/ CD_FRAMES
% 60;
175 msf
.cdmsf_frame0
= frame
% CD_FRAMES
;
177 msf
.start_m
= frame
/ (60*CD_FRAMES
);
178 msf
.start_s
= frame
/ CD_FRAMES
% 60;
179 msf
.start_f
= frame
% CD_FRAMES
;
182 //Bug fix: thanks to Martin Mitchell
183 //An out by one error that affects some CD players.
184 //Have to use endFrame-1 rather than endFrame (9/7/98)
185 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
186 msf
.cdmsf_min1
= (endFrame
-1) / (60*CD_FRAMES
);
187 msf
.cdmsf_sec1
= (endFrame
-1) / CD_FRAMES
% 60;
188 msf
.cdmsf_frame1
= (endFrame
-1) % CD_FRAMES
;
189 attemptNoDie(ioctl(cdDevice
, CDROMPLAYMSF
, &msf
),"playing CD",true);
191 msf
.end_m
= (endFrame
-1) / (60*CD_FRAMES
);
192 msf
.end_s
= (endFrame
-1) / CD_FRAMES
% 60;
193 msf
.end_f
= (endFrame
-1) % CD_FRAMES
;
194 attemptNoDie(ioctl(cdDevice
, CDIOCPLAYMSF
, (char *) &msf
),"playing CD",true);
198 void cdGetStatus(int &track
, int &frames
, SymbolID
&state
) {
199 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
200 cdrom_subchnl subchnl
;
201 subchnl
.cdsc_format
= CDROM_MSF
;
202 if (-1 == ioctl(cdDevice
, CDROMSUBCHNL
, &subchnl
)) {
204 ioc_read_subchannel subchnl
;
205 struct cd_sub_channel_info info
;
207 subchnl
.data
= &info
;
208 subchnl
.data_len
= sizeof (info
);
209 subchnl
.address_format
= CD_MSF_FORMAT
;
210 subchnl
.data_format
= CD_CURRENT_POSITION
;
212 if (-1 == ioctl(cdDevice
, CDIOCREADSUBCHANNEL
, (char *) &subchnl
)) {
216 state
= (state
== Open
? Open
: NoCD
); /* ? */
219 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
220 track
= subchnl
.cdsc_trk
;
221 frames
= subchnl
.cdsc_reladdr
.msf
.minute
*60*CD_FRAMES
+
222 subchnl
.cdsc_reladdr
.msf
.second
*CD_FRAMES
+
223 subchnl
.cdsc_reladdr
.msf
.frame
;
225 track
= subchnl
.data
->what
.position
.track_number
;
226 frames
= subchnl
.data
->what
.position
.reladdr
.msf
.minute
*60*CD_FRAMES
+
227 subchnl
.data
->what
.position
.reladdr
.msf
.second
*CD_FRAMES
+
228 subchnl
.data
->what
.position
.reladdr
.msf
.frame
;
231 SymbolID oldState
= state
;
232 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
233 switch(subchnl
.cdsc_audiostatus
) {
234 case CDROM_AUDIO_PAUSED
: state
= Pause
; break;
235 case CDROM_AUDIO_PLAY
: state
= Play
; break;
236 case CDROM_AUDIO_COMPLETED
: state
= Stop
; break;
237 case CDROM_AUDIO_NO_STATUS
: state
= Stop
; break;
238 case CDROM_AUDIO_INVALID
: state
= Stop
; break;
240 switch(subchnl
.data
->header
.audio_status
) {
241 case CD_AS_PLAY_PAUSED
: state
= Pause
; break;
242 case CD_AS_PLAY_IN_PROGRESS
: state
= Play
; break;
243 case CD_AS_PLAY_COMPLETED
: state
= Stop
; break;
244 case CD_AS_NO_STATUS
: state
= Stop
; break;
245 case CD_AS_AUDIO_INVALID
: state
= Stop
; break;
247 default : state
= NoCD
; break;
250 if ((oldState
== NoCD
|| oldState
== Open
) &&
251 (state
== Pause
|| state
== Play
|| state
== Stop
)) {
255 if (state
!= Play
&& state
!= Pause
) {
262 //attemptNoDie(ioctl(cdDevice, CDROMSTOP),"stopping CD");
263 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
264 ioctl(cdDevice
, CDROMSTOP
);
266 ioctl(cdDevice
, CDIOCSTOP
);
270 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
271 attemptNoDie(ioctl(cdDevice
, CDROMPAUSE
),"pausing CD",true);
273 attemptNoDie(ioctl(cdDevice
, CDIOCPAUSE
),"pausing CD",true);
276 void cdResume(void) {
277 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
278 attemptNoDie(ioctl(cdDevice
, CDROMRESUME
),"resuming CD",true);
280 attemptNoDie(ioctl(cdDevice
, CDIOCRESUME
),"resuming CD",true);
284 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
285 attemptNoDie(ioctl(cdDevice
, CDROMEJECT
),"ejecting CD",true);
287 attemptNoDie(ioctl(cdDevice
, CDIOCEJECT
),"ejecting CD",true);
290 void cdCloseTray(void) {
291 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
292 attemptNoDie(ioctl(cdDevice
, CDROMCLOSETRAY
),"ejecting CD",true);
294 attemptNoDie(ioctl(cdDevice
, CDIOCCLOSE
),"ejecting CD",true);
298 /* Sound Recording ================================================= */
301 #define SOUNDFORMAT AFMT_S16_LE
303 #define SOUNDFORMAT AFMT_S16_BE
306 //If kernel starts running out of sound memory playing mp3s, this could
307 //be the problem. OTOH if it is too small, it will start ticking on slow
309 #define MAXWINDOWSIZE 32
311 //Used for various buffers
312 #define BUFFERSIZE (1<<18)
314 static SoundSource source
;
315 static int inFrequency
, downFactor
, windowSize
, pipeIn
, device
;
318 //static int16_t *dataIn;
319 //static int dataInAvailable; // current size of data available
320 //static int feederFd; // fd of pipe to feeded process
321 char pipeBuffer
[BUFFERSIZE
];
322 int pipeBufferUsed
, pipeBufferDelay
;
326 int readWholeBlock(int pipe
,char *dest
,int length
) {
328 int result
= read(pipe
,dest
,length
);
337 int writeWholeBlock(int pipe
,char *src
,int length
) {
339 int result
= write(pipe
,src
,length
);
348 int makePiper(void) {
350 attempt(pipe(p
), "making pipe");
355 static char buffer
[BUFFERSIZE
];
358 bool doneReading
= false;
360 while(!doneReading
|| bufferUsed
>= 256) {
361 fd_set readers
, writers
;
363 if (bufferUsed
< BUFFERSIZE
-256) FD_SET(0, &readers
);
365 if (device_p
>= 4) FD_SET(p
[1], &writers
);
366 if (bufferUsed
-device_p
>= 256) FD_SET(device
, &writers
);
368 select((p
[1]<device
?device
:p
[1])+1, &readers
, &writers
, 0, 0);
370 if (FD_ISSET(device
, &writers
) && bufferUsed
-device_p
>= 256) {
371 int n
= write(device
, buffer
+device_p
, 256);
379 if (FD_ISSET(0, &readers
) && bufferUsed
< BUFFERSIZE
-256) {
380 int n
= read(0, buffer
+bufferUsed
, 256);
388 if (FD_ISSET(p
[1], &writers
) && device_p
>= 4) {
389 static char b2
[BUFFERSIZE
];
391 for(int i
=0;i
<device_p
;i
+= downFactor
*4) {
392 *(uint32_t*)(b2
+b2Used
) = *(uint32_t*)(buffer
+i
);
396 int n
= write(p
[1], b2
, b2Used
);
401 bufferUsed
-= n
*downFactor
;
402 device_p
-= n
*downFactor
;
403 memmove(buffer
, buffer
+n
*downFactor
, bufferUsed
);
414 void setupMixer(double &loudness
) {
415 int device
= open(mixer
,O_WRONLY
);
417 warning("opening mixer device");
419 if (source
!= SourcePipe
) {
420 int blah
= (source
== SourceCD
? SOUND_MASK_CD
: SOUND_MASK_LINE
+ SOUND_MASK_MIC
);
421 attemptNoDie(ioctl(device
,SOUND_MIXER_WRITE_RECSRC
,&blah
),"writing to mixer",true);
424 if (source
== SourceCD
) {
426 attemptNoDie(ioctl(device
,SOUND_MIXER_READ_CD
,&volume
),"writing to mixer",true);
427 loudness
= ((volume
&0xff) + volume
/256) / 200.0;
428 } else if (source
== SourceLine
) {
429 int volume
= 100*256 + 100;
430 attemptNoDie(ioctl(device
,SOUND_MIXER_READ_LINE
,&volume
),"writing to mixer",true);
431 //attemptNoDie(ioctl(device,SOUND_MIXER_READ_MIC,&volume),"writing to mixer");
432 loudness
= ((volume
&0xff) + volume
/256) / 200.0;
439 void setVolume(double loudness
) {
440 int device
= open(mixer
,O_WRONLY
);
442 warning("opening mixer device",true);
444 int scaledLoudness
= int(loudness
* 100.0);
445 if (scaledLoudness
< 0) scaledLoudness
= 0;
446 if (scaledLoudness
> 100) scaledLoudness
= 100;
447 scaledLoudness
= scaledLoudness
*256 + scaledLoudness
;
448 if (source
== SourceCD
) {
449 attemptNoDie(ioctl(device
,SOUND_MIXER_WRITE_CD
,&scaledLoudness
),"writing to mixer",true);
450 } else if (source
== SourceLine
) {
451 attemptNoDie(ioctl(device
,SOUND_MIXER_WRITE_LINE
,&scaledLoudness
),"writing to mixer",true);
452 attemptNoDie(ioctl(device
,SOUND_MIXER_WRITE_MIC
,&scaledLoudness
),"writing to mixer",true);
454 attemptNoDie(ioctl(device
,SOUND_MIXER_WRITE_PCM
,&scaledLoudness
),"writing to mixer",true);
460 void openSound(SoundSource source
, int inFrequency
, char *dspName
,
463 ::inFrequency
= inFrequency
;
469 if (source
== SourceESD
) {
471 attempt(pipeIn
= esd_monitor_stream(
472 ESD_BITS16
|ESD_STEREO
|ESD_STREAM
|ESD_PLAY
,Frequency
,0,PACKAGE
),
473 "connecting to EsounD");
476 attempt(esd
= esd_open_sound(0), "querying esd latency");
477 attempt(pipeBufferDelay
= esd_get_latency(esd_open_sound(0)), "querying esd latency");
480 pipeBufferDelay
= int( double(pipeBufferDelay
) * Frequency
* 2 / 44100 );
481 // there should be an extra factor of two here... but it looks wrong
482 // if i put it in... hmm
486 downFactor
= inFrequency
/ Frequency
;
490 int format
, stereo
, fragment
, fqc
;
492 #if defined(__FreeBSD__) || defined(_FreeBSD_kernel__)
493 attempt(device
= open(dspName
,O_WRONLY
),"opening dsp device",true);
494 format
= SOUNDFORMAT
;
495 attempt(ioctl(device
,SNDCTL_DSP_SETFMT
,&format
),"setting format",true);
496 if (format
!= SOUNDFORMAT
) error("setting format (2)");
499 if (source
== SourcePipe
)
500 attempt(device
= open(dspName
,O_WRONLY
),"opening dsp device",true);
502 attempt(device
= open(dspName
,O_RDONLY
),"opening dsp device",true);
504 format
= SOUNDFORMAT
;
505 fqc
= (source
== SourcePipe
? inFrequency
: Frequency
);
508 //if (source == SourcePipe)
509 //fragment = 0x00010000*(MAXWINDOWSIZE+1) + (LogSize-Overlap+1);
511 //Added extra fragments to allow recording overrun (9/7/98)
512 //8 fragments of size 2*(2^(LogSize-Overlap+1)) bytes
513 // fragment = 0x00080000 + (LogSize-Overlap+1);
515 fragment
= 0x0003000e;
516 attemptNoDie(ioctl(device
,SNDCTL_DSP_SETFRAGMENT
,&fragment
),"setting fragment",true);
518 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
519 attempt(ioctl(device
,SNDCTL_DSP_SETFMT
,&format
),"setting format",true);
520 if (format
!= SOUNDFORMAT
) error("setting format (2)");
522 attempt(ioctl(device
,SNDCTL_DSP_STEREO
,&stereo
),"setting stereo",true);
523 attemptNoDie(ioctl(device
,SNDCTL_DSP_SPEED
,&fqc
),"setting frequency",true);
525 if (source
== SourcePipe
) {
526 //dataIn = new int16_t[NumSamples*2*downFactor*MAXWINDOWSIZE];
527 //memset(dataIn,0,NumSamples*4*downFactor*MAXWINDOWSIZE);
528 //dataInAvailable = NumSamples*4*downFactor*windowSize;
529 //pipeIn = 0;//dup(0);
531 pipeIn
= makePiper();
537 fcntl(pipeIn
, F_SETFL
, O_NONBLOCK
);
539 data
= new int16_t[NumSamples
*2];
540 memset((char*)data
,0,NumSamples
*4);
549 int getNextFragment(void) {
550 // Linux wasn't designed for real time piping
551 // hence, this section is a bit hacked...
552 static int step
= 4096;
554 bool end
= false, any
= false;
556 // This seems to be necessary. Not sure why.
559 // Read all data in pipe
560 while(pipeBufferUsed
< BUFFERSIZE
) {
561 int result
= read(pipeIn
,pipeBuffer
+pipeBufferUsed
,BUFFERSIZE
-pipeBufferUsed
);
566 pipeBufferUsed
+= result
;
569 //printf("%d\n",pipeBufferUsed);
570 //if (pipeBufferUsed == BUFFERSIZE) {
574 int eat
= pipeBufferUsed
- pipeBufferDelay
;
576 if (eat
< step
&& step
> 256) step
-= 128;
577 else if (eat
> step
* 16) step
+= 128;
580 if (eat
> step
) eat
= step
;
582 if (eat
> NumSamples
*4)
583 memcpy((char*)data
, pipeBuffer
+eat
-NumSamples
*4, NumSamples
*4);
585 memmove((char*)data
,(char*)data
+eat
,NumSamples
*4-eat
);
586 memcpy((char*)data
+NumSamples
*4-eat
,pipeBuffer
,eat
);
589 pipeBufferUsed
-= eat
;
590 memmove(pipeBuffer
,pipeBuffer
+eat
,pipeBufferUsed
);
593 return end
&& eat
<= 0 ? -1 : 0;