#include #include #include #include #include #include #include #include #include #include #include "byteorder.h" #include "sound.h" #include "buffer.moc" extern KLocale *globalKlocale; /* ---------------------------------------------------------------------- */ void xperror(const char *msg) { char text[256]; sprintf(text,"%s: %s", msg, strerror(errno)); KMessageBox::error(NULL, text); } /* ---------------------------------------------------------------------- */ AudioBuffer::AudioBuffer() { size = 0; busy = 0; position = 0; clippedsamples = 0; } AudioBuffer::~AudioBuffer() {} int AudioBuffer::is_busy() { return busy; } void AudioBuffer::balloc(void) { busy++; if (busy != 1) fprintf(stderr,"alloc: buffer reference count bug: %d\n",busy); } void AudioBuffer::bfree(void) { busy--; if (busy != 0) fprintf(stderr,"free: buffer reference count bug: %d\n",busy); } int AudioBuffer::start_write(struct SOUNDPARAMS*) { return 0; } void AudioBuffer::stop_write() {} struct SOUNDPARAMS* AudioBuffer::get_params() { return NULL; } int AudioBuffer::get_size() { return 0; } char* AudioBuffer::name() { return NULL; } void* AudioBuffer::read_audio(int len) { return 0; } int AudioBuffer::write_audio(int len, void *data) { return 0; } int AudioBuffer::seek(int pos) { return 0; } int AudioBuffer::tell() { return 0; } /* ---------------------------------------------------------------------- */ #define BUFFER_SIZE 0x10000 #define BUFFER_MASK 0x0ffff #define BUFFER_SHIFT 16 RAMBuffer::RAMBuffer() { static int counter = 0; memset(¶ms,0,sizeof(struct SOUNDPARAMS)); sprintf(bufname,"buffer #%d",++counter); buffers = (char**)malloc(sizeof(void*)); buffers[0] = (char*)malloc(BUFFER_SIZE); bufcount = 1; position = 0; } RAMBuffer::~RAMBuffer() { int i; for (i = 0; i < bufcount; i++) { free(buffers[i]); } free(buffers); } int RAMBuffer::start_write(struct SOUNDPARAMS *p) { memcpy(¶ms,p,sizeof(struct SOUNDPARAMS)); return 0; } void RAMBuffer::stop_write() {}; struct SOUNDPARAMS* RAMBuffer::get_params() { return ¶ms; } int RAMBuffer::get_size() { return size; } char* RAMBuffer::name() { return bufname; } void* RAMBuffer::read_audio(int len) { /* FIXME (?): one can't read over buffer boundaries */ char *ptr; if (position+len > size) return NULL; ptr = buffers[position>>BUFFER_SHIFT]; ptr += (position&BUFFER_MASK); position += len; return ptr; } int RAMBuffer::write_audio(int len, void *data) { /* dito for write - not over buffer boundaries */ if ((position>>BUFFER_SHIFT) >= bufcount) { buffers = (char**)realloc(buffers,sizeof(void*)*(bufcount+1)); if (NULL == (buffers[bufcount] = (char*)malloc(BUFFER_SIZE))) return -1; bufcount++; } memcpy(buffers[position>>BUFFER_SHIFT]+(position&BUFFER_MASK), data, len); position += len; if (size < position) size = position; return 0; } int RAMBuffer::seek(int pos) { if (pos > size) return -1; position = pos; return position; } int RAMBuffer::tell() { return position; } /* ---------------------------------------------------------------------- */ FileBuffer::FileBuffer() { memset(¶ms,0,sizeof(struct SOUNDPARAMS)); fd = -1; size = position = 0; bstart = bstop = 0; } FileBuffer::~FileBuffer() { if (fd == -1) return; close(fd); } void FileBuffer::init_header() { /* stolen from cdda2wav */ int nBitsPerSample = 8; if (params.format == FMT_16BIT) nBitsPerSample = 16; unsigned long nBlockAlign = params.channels * ((nBitsPerSample + 7) / 8); unsigned long nAvgBytesPerSec = nBlockAlign * params.rate; unsigned long temp = /* data length */ 0 + sizeof(WAVEHDR) - sizeof(CHUNKHDR); fileheader.chkRiff.ckid = cpu_to_le32(FOURCC_RIFF); fileheader.fccWave = cpu_to_le32(FOURCC_WAVE); fileheader.chkFmt.ckid = cpu_to_le32(FOURCC_FMT); fileheader.chkFmt.dwSize = cpu_to_le32(16); fileheader.wFormatTag = cpu_to_le16(WAVE_FORMAT_PCM); fileheader.nChannels = cpu_to_le16(params.channels); fileheader.nSamplesPerSec = cpu_to_le32(params.rate); fileheader.nAvgBytesPerSec = cpu_to_le32(nAvgBytesPerSec); fileheader.nBlockAlign = cpu_to_le16(nBlockAlign); fileheader.wBitsPerSample = cpu_to_le16(nBitsPerSample); fileheader.chkData.ckid = cpu_to_le32(FOURCC_DATA); fileheader.chkRiff.dwSize = cpu_to_le32(temp); fileheader.chkData.dwSize = cpu_to_le32(0 /* data length */); } int FileBuffer::attach(const char *file) { int new_file = 0; if (-1 != fd) close(fd); ro = 0; position = 0; size = 0; offset = sizeof(WAVEHDR); strcpy(filename,file); if (-1 == (fd = open(filename,O_RDWR))) { if (errno == ENOENT) { if (-1 == (fd = open(filename,O_RDWR|O_CREAT,0666))) { xperror(i18n("can't create wav file")); return -1; } new_file = 1; } else { if (-1 == (fd = open(filename,O_RDONLY))) { xperror(i18n("can't open wav file")); return -1; } else ro = 1; } } fcntl(fd,F_SETFD,FD_CLOEXEC); if (!new_file) { read(fd,&fileheader,offset); if (!IS_STD_WAV_HEADER(fileheader)) { #if 0 /* nice for debugging, but annonying for everyday usage */ KMessageBox::error(NULL, i18n("not a wav file")); #endif return -1; } if (le16_to_cpu(fileheader.wFormatTag) != WAVE_FORMAT_PCM) { KMessageBox::error(NULL,i18n("unsupported audio format")); return -1; } params.format = FMT_8BIT; if (16 == le16_to_cpu(fileheader.wBitsPerSample)) params.format = FMT_16BIT; params.channels = le16_to_cpu(fileheader.nChannels); params.rate = cpu_to_le32(fileheader.nSamplesPerSec); size = le32_to_cpu(fileheader.chkData.dwSize); } return 0; } int FileBuffer::start_write(struct SOUNDPARAMS *p) { memcpy(¶ms,p,sizeof(struct SOUNDPARAMS)); init_header(); lseek(fd,0,SEEK_SET); write(fd,&fileheader,offset); return 0; } void FileBuffer::stop_write() { unsigned long temp = size + sizeof(WAVEHDR) - sizeof(CHUNKHDR); fileheader.chkRiff.dwSize = cpu_to_le32(temp); fileheader.chkData.dwSize = cpu_to_le32(size); lseek(fd,0,SEEK_SET); write(fd,&fileheader,offset); } struct SOUNDPARAMS* FileBuffer::get_params() { return ¶ms; } int FileBuffer::get_size() { return size; } char* FileBuffer::name() { return filename; } void* FileBuffer::read_audio(int len) { int rc; if (position+len > size) return NULL; /* printf("[%d - %d (+%d) - %d] / %d\n",bstart,position,len,bstop,size); */ if (position < bstart || position+len > bstop) { rc = read(fd,buffer,65536); #if BYTE_ORDER == BIG_ENDIAN if (params.format == FMT_16BIT) { /* byteswap 16bit pcm on bigendian machines */ int i; char h; for (i = 0; i < rc; i += 2) { h = buffer[i]; buffer[i] = buffer[i+1]; buffer[i+1] = h; } } #endif if (-1 == rc) return NULL; bstart = position, bstop = position+rc; } position += len; return buffer+position-bstart-len; } int FileBuffer::write_audio(int len, void *data) { int rc; #if BYTE_ORDER == BIG_ENDIAN char *buf; int i; if (params.format == FMT_16BIT) { /* byteswap 16bit pcm on bigendian machines */ buf = (char*)malloc(len); for (i = 0; i < len; i += 2) { buf[i] = ((char*)data)[i+1]; buf[i+1] = ((char*)data)[i]; } rc = write(fd,buf,len); free(buf); } else { rc = write(fd,data,len); } #else rc = write(fd,data,len); #endif if (len == rc) { position += len; if (position > size) size = position; return 0; } else return -1; } int FileBuffer::seek(int pos) { if (pos > size) return -1; position = pos; if (-1 == lseek(fd,position+offset,SEEK_SET)) perror("fb: lseek"); return 0; } int FileBuffer::tell() { return position; } /* ---------------------------------------------------------------------- */ BufferList::BufferList(QListBox *l, Soundcard *c) { listbox = l; card = c; count = 0; brecord = -1; bplayback = -1; mon = 0; level = 0; wait = 0; new_buffer_count = 0; connect(card,SIGNAL(newparams(struct SOUNDPARAMS*)), this, SLOT(new_params(struct SOUNDPARAMS*))); connect(card,SIGNAL(senddata(void*)), this, SLOT(new_data(void*))); connect(card,SIGNAL(receivedata(void*)), this, SLOT(post_data(void*))); } int BufferList::add_filebuffer(const char *filename) { FileBuffer *fbuffer; fbuffer = new FileBuffer(); if (-1 == fbuffer->attach(filename)) { delete fbuffer; return -1; } add_buffer(fbuffer); return 0; } int BufferList::add_rambuffer() { add_buffer(new RAMBuffer()); return 0; } void BufferList::add_buffer(AudioBuffer *buf) { if (count) buffers = (AudioBuffer**)realloc (buffers,sizeof(AudioBuffer*)*(count+1)); else buffers = (AudioBuffer**)malloc(sizeof(AudioBuffer*)); buffers[count] = buf; listbox->insertItem("*",count); listbox->setCurrentItem(count); label_buffer(count); count++; } void BufferList::label_buffer(int buf) { struct SOUNDPARAMS *p; int sec,len,size; char text[256]; p = buffers[buf]->get_params(); if (p->format != FMT_UNDEFINED) { size = buffers[buf]->get_size(); sec = size/p->rate/p->channels; if (p->format == FMT_16BIT) sec /= 2; len = sprintf(text,"%5d %s %s %d:%02d / ", p->rate, (p->channels == 1) ? (const char*) i18n("mono") : (const char*) i18n("stereo"), sndfmt2str(p->format), sec/60,sec%60); if (size>>20) len += sprintf(text+len,"%d.%dMB ", size>>20,((size&0xfffff)*10)>>20); else len += sprintf(text+len,"%dkB ",size>>10); } else { strcpy(text,i18n("new")); strcat(text," "); } strcat(text,buffers[buf]->name()); if (p->format != FMT_UNDEFINED && buffers[buf]->clippedsamples) { sprintf(text+strlen(text)," (%2d samples clipped)", buffers[buf]->clippedsamples); } listbox->changeItem(text,buf); } void BufferList::del_buffer(int buf) { int i; if (buffers[buf]->is_busy()) { KMessageBox::error(NULL, i18n("buffer is busy")); return; } listbox->removeItem(buf); delete buffers[buf]; for (i = buf+1; i < count; i++) buffers[i-1] = buffers[i]; count--; if (0 == count) free(buffers); } void BufferList::new_params(struct SOUNDPARAMS *p) { memcpy(¶ms,p,sizeof(struct SOUNDPARAMS)); } void BufferList::new_data(void *data) { static int count; int i,j,max; unsigned char *c; short *s; int clippedhere=0; if (wait) { if (params.format == FMT_16BIT) { for (i = (params.blocksize>>1)-1, s=(short*)data, max = 0; i; i--) if (abs(s[i]) > max) max = abs(s[i]); max = max * 100 / 32768; } else { for (i = params.blocksize-1, c=(unsigned char*)data, max = 0; i; i--) if ((j = abs((int)c[i]-128)) > max) max = j; max = max * 100 / 128; } if (max >= level) { wait = 0; emit status(i18n("record")); } else return; } /* we're here if we're not waiting */ /* we have to check for possible clipping */ if (params.format == FMT_16BIT) { for (i = (params.blocksize>>1)-1, s=(short*)data, max = 0; i; i--) if ((s[i]==32767) || (s[i]==-32768)) { //fprintf(stderr,"!"); clippedhere++; } max = max * 100 / 32768; } else { for (i = params.blocksize-1, c=(unsigned char*)data, max = 0; i; i--) { j = abs((int)c[i]-128); if ((j==127) || (j==-128)) { //fprintf(stderr,"!"); clippedhere++; } } max = max * 100 / 128; } //fprintf(stderr,"%d\n",clippedhere); if (-1 != brecord) { buffers[brecord]->clippedsamples += clippedhere; if (-1 == buffers[brecord]->write_audio(params.blocksize,data)) { istop(); xperror(i18n("can't save sound data")); } if (++count == 16) { count = 0; label_buffer(brecord); set_pos_size(i18n("record"),brecord); } } } void BufferList::post_data(void *data) { static int count = 0; void *ptr; if (NULL == (ptr = buffers[bplayback]->read_audio(params.blocksize))) { memset(data,0,params.blocksize); istop(); //fprintf(stderr,"->EOF"); return; } memcpy(data,ptr,params.blocksize); if (++count == 16) { count = 0; set_pos_size(i18n("playback"),bplayback); } } void BufferList::set_pos_size(const char *start, int buf) { struct SOUNDPARAMS *p; int sec,len,size; char text[256]; p = buffers[buf]->get_params(); size = buffers[buf]->tell(); sec = size/p->rate/p->channels; if (p->format == FMT_16BIT) sec /= 2; len = sprintf(text,"%s %d:%02d / ",start,sec/60,sec%60); size = buffers[buf]->get_size(); sec = size/p->rate/p->channels; if (p->format == FMT_16BIT) sec /= 2; sprintf(text+len,"%d:%02d",sec/60,sec%60); emit status(text); } /* ---------------------------------------------------------------------- */ void BufferList::new_ram() { add_rambuffer(); } void BufferList::save_buf(const char *filename) { int i; void *data; struct SOUNDPARAMS *p; FileBuffer fbuf; if (-1 == (i = listbox->currentItem())) return; if (buffers[i]->is_busy()) { KMessageBox::error(NULL, i18n("buffer is busy")); return; } p = buffers[i]->get_params(); buffers[i]->seek(0); if (-1 == fbuf.attach(filename)) return; fbuf.start_write(p); while (NULL != (data = buffers[i]->read_audio(p->blocksize))) { if (-1 == fbuf.write_audio(p->blocksize,data)) { xperror(i18n("can't write sound data")); break; } } fbuf.stop_write(); } void BufferList::del_buf() { int i; if (-1 != (i = listbox->currentItem())) del_buffer(i); } void BufferList::monitor() { mon = !mon; if (mon) { if (-1 == card->start_record()) { xperror(i18n("can't open soundcard")); return; } emit status(i18n("monitor")); } else { if (-1 == brecord) { card->stop(); emit status(i18n("idle")); } } } void BufferList::set_level(int l) { level = l; } void BufferList::record() { if (-1 != bplayback || -1 != brecord) stop(); if (-1 == (brecord = listbox->currentItem())) { add_rambuffer(); brecord = count-1; } if (buffers[brecord]->is_busy()) { KMessageBox::error(NULL, i18n("buffer is busy")); brecord = -1; return; } if (-1 == card->start_record()) { xperror(i18n("can't open soundcard")); brecord = -1; return; } buffers[brecord]->balloc(); buffers[brecord]->start_write(¶ms); buffers[brecord]->seek(0); label_buffer(brecord); if (level) { wait = 1; emit status(i18n("waiting...")); } else { wait = 0; emit status(i18n("recording...")); } } void BufferList::play() { struct SOUNDPARAMS *p; if (-1 != bplayback || -1 != brecord) stop(); if (-1 == (bplayback = listbox->currentItem())) { /* error message ?? */ bplayback = -1; return; } if (buffers[bplayback]->is_busy()) { KMessageBox::error(NULL, i18n("buffer is busy")); bplayback = -1; return; } p = buffers[bplayback]->get_params(); card->setparams(p); if (-1 == card->start_playback()) { xperror(i18n("can't open soundcard")); bplayback = -1; return; } buffers[bplayback]->balloc(); buffers[bplayback]->seek(0); emit status(i18n("playback")); } #define JUMP_SECONDS 20 void BufferList::forward() { struct SOUNDPARAMS *p; int pos; if(-1 != bplayback) { p = buffers[bplayback]->get_params(); pos = buffers[bplayback]->tell(); pos += (JUMP_SECONDS * p->rate * p->channels * ((p->format == FMT_16BIT) ? 2 : 1)) & ~(2^14-1); buffers[bplayback]->seek(pos); card->kill_buffer(); } } void BufferList::backward() { struct SOUNDPARAMS *p; int pos; if(-1 != bplayback) { p = buffers[bplayback]->get_params(); pos = buffers[bplayback]->tell(); pos -= (JUMP_SECONDS * p->rate * p->channels * ((p->format == FMT_16BIT) ? 2 : 1)) & ~(2^14-1); if (pos < 0) pos = 0; buffers[bplayback]->seek(pos); card->kill_buffer(); } } void BufferList::istop() { if (-1 != brecord) { buffers[brecord]->stop_write(); label_buffer(brecord); buffers[brecord]->bfree(); brecord = -1; if (!mon) { card->stop(); emit status(i18n("idle")); } else { emit status(i18n("monitor")); } } if (-1 != bplayback) { buffers[bplayback]->bfree(); bplayback = -1; if (mon) { card->start_record(); emit status(i18n("monitor")); } else { card->stop(); emit status(i18n("idle")); } } } void BufferList::stop() { if (-1 != bplayback) { /* stop *button*, we kill the output buffer */ card->kill_buffer(); } istop(); } void BufferList::next_buffer() { char filename[256]; if (-1 != bplayback) { listbox->setCurrentItem(bplayback+1); stop(); play(); } else if (-1 != brecord) { istop(); sprintf(filename,"song%03d.wav",new_buffer_count++); add_filebuffer(filename); record(); } else { sprintf(filename,"song%03d.wav",new_buffer_count++); add_filebuffer(filename); record(); } }