diff options
Diffstat (limited to 'buffer.cpp')
-rw-r--r-- | buffer.cpp | 841 |
1 files changed, 841 insertions, 0 deletions
diff --git a/buffer.cpp b/buffer.cpp new file mode 100644 index 0000000..ea32e93 --- /dev/null +++ b/buffer.cpp @@ -0,0 +1,841 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#include <qobject.h> +#include <qlistbox.h> + +#include <kmessagebox.h> +#include <klocale.h> + +#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(); + } +} |