aboutsummaryrefslogtreecommitdiffstats
path: root/buffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'buffer.cpp')
-rw-r--r--buffer.cpp841
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(&params,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(&params,p,sizeof(struct SOUNDPARAMS));
+ return 0;
+}
+
+void RAMBuffer::stop_write() {};
+
+struct SOUNDPARAMS*
+RAMBuffer::get_params()
+{
+ return &params;
+}
+
+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(&params,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(&params,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 &params;
+}
+
+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(&params,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(&params);
+ 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();
+ }
+}