[Git]Ignore work files.
[synaesthesia.git] / sound.cc
blobe16ec0bdcc2119d1fad6b25cf23c577966e41b75
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
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <sys/ioctl.h>
27 #include <sys/time.h>
28 #include <sys/resource.h>
29 #include <sys/types.h>
30 #include <sys/ipc.h>
31 #include <sys/sem.h>
32 #include <sys/shm.h>
33 #include <sys/wait.h>
34 #include <fcntl.h>
35 #include <unistd.h>
36 #include <signal.h>
37 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
38 #include <linux/soundcard.h>
39 #include <linux/cdrom.h>
40 //#include <linux/ucdrom.h>
41 #else
42 #include <sys/soundcard.h>
43 #include <sys/cdio.h>
44 #define CDROM_LEADOUT 0xAA
45 #define CD_FRAMES 75 /* frames per second */
46 #define CDROM_DATA_TRACK 0x4
47 #endif
48 #include <time.h>
50 #include <stdlib.h>
51 #include <stdint.h>
52 #include <math.h>
53 #include <string.h>
54 #include "syna.h"
56 #if HAVE_LIBESD
57 #include <esd.h>
58 #endif
60 static int cdDevice;
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);
70 void cdClose(void) {
71 close(cdDevice);
73 delete[] trackFrame;
76 //void cdConfigure(void) {
77 //#ifdef HAVE_UCDROM
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");
82 // cdClose();
83 //#endif
84 //}
86 void getTrackInfo(void) {
87 delete trackFrame;
88 trackFrame = 0;
89 trackCount = 0;
91 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
92 cdrom_tochdr cdTochdr;
93 if (-1 == ioctl(cdDevice, CDROMREADTOCHDR, &cdTochdr))
94 #else
95 ioc_toc_header cdTochdr;
96 if (-1 == ioctl(cdDevice, CDIOREADTOCHEADER, (char *)&cdTochdr))
97 #endif
98 return;
99 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
100 trackCount = cdTochdr.cdth_trk1;
101 #else
102 trackCount = cdTochdr.ending_track - cdTochdr.starting_track + 1;
103 #endif
105 int i;
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);
112 #else
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;
119 #endif
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]);
128 else
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;
132 #else
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]);
136 else
137 trackFrame[i] = cdTocentry.addr.msf.minute*60*CD_FRAMES+
138 cdTocentry.addr.msf.second*CD_FRAMES+
139 cdTocentry.addr.msf.frame;
140 #endif
144 int cdGetTrackCount(void) {
145 return trackCount;
147 int cdGetTrackFrame(int track) {
148 if (!trackFrame)
149 return 0;
150 else if (track <= 1 || track > trackCount+1)
151 return trackFrame[0];
152 else
153 return trackFrame[track-1];
156 void cdPlay(int frame, int endFrame) {
157 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
158 cdrom_msf msf;
159 #else
160 struct ioc_play_msf msf;
161 #endif
162 if (frame < 1) frame = 1;
163 if (endFrame < 1) {
164 if (!trackFrame) return;
165 endFrame = trackFrame[trackCount];
168 //Some CDs can't change tracks unless not playing.
169 // (Sybren Stuvel)
170 cdStop();
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;
176 #else
177 msf.start_m = frame / (60*CD_FRAMES);
178 msf.start_s = frame / CD_FRAMES % 60;
179 msf.start_f = frame % CD_FRAMES;
180 #endif
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);
190 #else
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);
195 #endif
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)) {
203 #else
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)) {
213 #endif
214 track = 1;
215 frames = 0;
216 state = (state == Open ? Open : NoCD); /* ? */
217 return;
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;
224 #else
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;
229 #endif
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;
239 #else
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;
246 #endif
247 default : state = NoCD; break;
250 if ((oldState == NoCD || oldState == Open) &&
251 (state == Pause || state == Play || state == Stop)) {
252 getTrackInfo();
255 if (state != Play && state != Pause) {
256 track = 1;
257 frames = 0;
261 void cdStop(void) {
262 //attemptNoDie(ioctl(cdDevice, CDROMSTOP),"stopping CD");
263 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
264 ioctl(cdDevice, CDROMSTOP);
265 #else
266 ioctl(cdDevice, CDIOCSTOP);
267 #endif
269 void cdPause(void) {
270 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
271 attemptNoDie(ioctl(cdDevice, CDROMPAUSE),"pausing CD",true);
272 #else
273 attemptNoDie(ioctl(cdDevice, CDIOCPAUSE),"pausing CD",true);
274 #endif
276 void cdResume(void) {
277 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
278 attemptNoDie(ioctl(cdDevice, CDROMRESUME),"resuming CD",true);
279 #else
280 attemptNoDie(ioctl(cdDevice, CDIOCRESUME),"resuming CD",true);
281 #endif
283 void cdEject(void) {
284 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
285 attemptNoDie(ioctl(cdDevice, CDROMEJECT),"ejecting CD",true);
286 #else
287 attemptNoDie(ioctl(cdDevice, CDIOCEJECT),"ejecting CD",true);
288 #endif
290 void cdCloseTray(void) {
291 #if !defined (__FreeBSD__) && !defined(__FreeBSD_kernel__)
292 attemptNoDie(ioctl(cdDevice, CDROMCLOSETRAY),"ejecting CD",true);
293 #else
294 attemptNoDie(ioctl(cdDevice, CDIOCCLOSE),"ejecting CD",true);
295 #endif
298 /* Sound Recording ================================================= */
300 #ifdef LITTLEENDIAN
301 #define SOUNDFORMAT AFMT_S16_LE
302 #else
303 #define SOUNDFORMAT AFMT_S16_BE
304 #endif
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
308 //computers
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;
317 // pipe stuff
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;
324 static char *mixer;
326 int readWholeBlock(int pipe,char *dest,int length) {
327 while(length > 0) {
328 int result = read(pipe,dest,length);
329 if (result < 1)
330 return -1;
331 dest += result;
332 length -= result;
334 return 0;
337 int writeWholeBlock(int pipe,char *src,int length) {
338 while(length > 0) {
339 int result = write(pipe,src,length);
340 if (result < 1)
341 return -1;
342 src += result;
343 length -= result;
345 return 0;
348 int makePiper(void) {
349 int p[2];
350 attempt(pipe(p), "making pipe");
352 if (!fork()) {
353 close(p[0]);
355 static char buffer[BUFFERSIZE];
356 int bufferUsed = 0;
357 int device_p = 0;
358 bool doneReading = false;
360 while(!doneReading || bufferUsed >= 256) {
361 fd_set readers, writers;
362 FD_ZERO(&readers);
363 if (bufferUsed < BUFFERSIZE-256) FD_SET(0, &readers);
364 FD_ZERO(&writers);
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);
373 if (n < 1)
374 break;
376 device_p += n;
379 if (FD_ISSET(0, &readers) && bufferUsed < BUFFERSIZE-256) {
380 int n = read(0, buffer+bufferUsed, 256);
382 if (n < 1)
383 doneReading = true;
385 bufferUsed += n;
388 if (FD_ISSET(p[1], &writers) && device_p >= 4) {
389 static char b2[BUFFERSIZE];
390 int b2Used = 0;
391 for(int i=0;i<device_p;i += downFactor*4) {
392 *(uint32_t*)(b2+b2Used) = *(uint32_t*)(buffer+i);
393 b2Used += 4;
396 int n = write(p[1], b2, b2Used);
398 if (n < 1)
399 break;
401 bufferUsed -= n*downFactor;
402 device_p -= n*downFactor;
403 memmove(buffer, buffer+n*downFactor, bufferUsed);
407 exit(0);
410 close(p[1]);
411 return p[0];
414 void setupMixer(double &loudness) {
415 int device = open(mixer,O_WRONLY);
416 if (device == -1)
417 warning("opening mixer device");
418 else {
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) {
425 int volume;
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;
433 } else
434 loudness = 1.0;
435 close(device);
439 void setVolume(double loudness) {
440 int device = open(mixer,O_WRONLY);
441 if (device == -1)
442 warning("opening mixer device",true);
443 else {
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);
453 } else {
454 attemptNoDie(ioctl(device,SOUND_MIXER_WRITE_PCM,&scaledLoudness),"writing to mixer",true);
456 close(device);
460 void openSound(SoundSource source, int inFrequency, char *dspName,
461 char *mixerName) {
462 ::source = source;
463 ::inFrequency = inFrequency;
464 ::windowSize = 1;
465 pipeBufferUsed = 0;
466 pipeBufferDelay = 0;
467 mixer = mixerName;
469 if (source == SourceESD) {
470 #if HAVE_LIBESD
471 attempt(pipeIn = esd_monitor_stream(
472 ESD_BITS16|ESD_STEREO|ESD_STREAM|ESD_PLAY,Frequency,0,PACKAGE),
473 "connecting to EsounD");
475 int esd;
476 attempt(esd = esd_open_sound(0), "querying esd latency");
477 attempt(pipeBufferDelay = esd_get_latency(esd_open_sound(0)), "querying esd latency");
478 esd_close(esd);
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
484 #endif
485 } else {
486 downFactor = inFrequency / Frequency;
487 if (downFactor <= 0)
488 downFactor = 1;
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)");
497 close(device);
498 #endif
499 if (source == SourcePipe)
500 attempt(device = open(dspName,O_WRONLY),"opening dsp device",true);
501 else
502 attempt(device = open(dspName,O_RDONLY),"opening dsp device",true);
504 format = SOUNDFORMAT;
505 fqc = (source == SourcePipe ? inFrequency : Frequency);
506 stereo = 1;
508 //if (source == SourcePipe)
509 //fragment = 0x00010000*(MAXWINDOWSIZE+1) + (LogSize-Overlap+1);
510 //else
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)");
521 #endif
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();
532 } else {
533 pipeIn = device;
537 fcntl(pipeIn, F_SETFL, O_NONBLOCK);
539 data = new int16_t[NumSamples*2];
540 memset((char*)data,0,NumSamples*4);
543 void closeSound() {
544 delete data;
546 close(pipeIn);
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.
557 usleep(1000);
559 // Read all data in pipe
560 while(pipeBufferUsed < BUFFERSIZE) {
561 int result = read(pipeIn,pipeBuffer+pipeBufferUsed,BUFFERSIZE-pipeBufferUsed);
562 if (result == 0)
563 end = true;
564 if (result < 1)
565 break;
566 pipeBufferUsed += result;
569 //printf("%d\n",pipeBufferUsed);
570 //if (pipeBufferUsed == BUFFERSIZE) {
571 // delay += 10000;
574 int eat = pipeBufferUsed - pipeBufferDelay;
576 if (eat < step && step > 256) step -= 128;
577 else if (eat > step * 16) step += 128;
579 if (eat > 0) {
580 if (eat > step) eat = step;
582 if (eat > NumSamples*4)
583 memcpy((char*)data, pipeBuffer+eat-NumSamples*4, NumSamples*4);
584 else {
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;