aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--GNUmakefile153
-rw-r--r--INSTALL59
-rw-r--r--buffer.cpp841
-rw-r--r--buffer.h192
-rw-r--r--byteorder.h49
-rw-r--r--fft.cpp218
-rw-r--r--fft.h40
-rwxr-xr-ximg/1rightarrow.pngbin0 -> 431 bytes
-rwxr-xr-ximg/2leftarrow.pngbin0 -> 740 bytes
-rwxr-xr-ximg/2rightarrow.pngbin0 -> 418 bytes
-rwxr-xr-ximg/freq.pngbin0 -> 362 bytes
-rwxr-xr-ximg/level.pngbin0 -> 497 bytes
-rwxr-xr-ximg/line_monitor.pngbin0 -> 733 bytes
-rwxr-xr-ximg/mrecord.pngbin0 -> 389 bytes
-rwxr-xr-ximg/player_stop.pngbin0 -> 345 bytes
-rw-r--r--index.html118
-rw-r--r--krecord.cpp658
-rw-r--r--krecord.h122
-rw-r--r--level.cpp438
-rw-r--r--level.h56
-rw-r--r--mix.c75
-rw-r--r--mk/Autoconf.mk138
-rw-r--r--mk/Compile.mk88
-rw-r--r--mk/Maintainer.mk12
-rw-r--r--mk/Variables.mk46
-rw-r--r--oss.cpp358
-rw-r--r--oss.h68
-rw-r--r--sound.cpp272
-rw-r--r--sound.h76
-rw-r--r--soundfft.c196
-rw-r--r--soundfft.h5
-rw-r--r--sunaudio.cpp369
-rw-r--r--sunaudio.h68
33 files changed, 4715 insertions, 0 deletions
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..00b2988
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,153 @@
+# configuration
+-include Make.config
+include mk/Variables.mk
+
+# QT/KDE dirs
+ifeq ($(QTDIR),)
+QTDIR := /usr
+foobar := $(shell echo "WARNING: QTDIR isn't set, assuming /usr" >&2)
+endif
+ifeq ($(KDEDIR),)
+KDEDIR := /usr
+foobar := $(shell echo "WARNING: KDEDIR isn't set, assuming /usr" >&2)
+endif
+prefix := $(KDEDIR)
+export QTDIR KDEDIR
+
+# kde install directories
+appsdir := $(DESTDIR)$(shell sh kdedirs apps)/Multimedia
+datadir := $(DESTDIR)$(shell sh kdedirs data)/krecord
+htmldir := $(DESTDIR)$(shell sh kdedirs html)/en/krecord
+
+# default target
+all: build
+
+
+##########################################################################
+# fixup flags depending on the environment variables (QTDIR, KDEDIR)
+
+# X11R6
+X11DIR := /usr/X11R6
+CFLAGS += -I$(X11DIR)/include
+LDFLAGS += -L$(X11DIR)/$(LIB)
+
+# Qt
+ifeq ($(QTDIR),/usr)
+CFLAGS += -I/usr/include/qt
+else
+CFLAGS += -I$(QTDIR)/include
+LDFLAGS += -L$(QTDIR)/$(LIB)
+endif
+
+# KDE
+ifeq ($(KDEDIR),/usr)
+CFLAGS += -I/usr/include/kde
+else
+CFLAGS += -I$(KDEDIR)/include
+LDFLAGS += -L$(KDEDIR)/$(LIB)
+endif
+
+# version
+CFLAGS += -DKRECORD_VERSION='"$(VERSION)"'
+CFLAGS += -fno-strict-aliasing # fft code needs this
+
+
+##########################################################################
+# poor man's autoconf
+
+include mk/Autoconf.mk
+
+define make-config
+LIB := $(LIB)
+HAVE_SOUNDCARD_H := $(call ac_header,soundcard.h)
+HAVE_SYS_SOUNDCARD_H := $(call ac_header,sys/soundcard.h)
+HAVE_SUN_AUDIOIO_H := $(call ac_header,sun/audioio.h)
+HAVE_LIBQT_MT := $(call ac_lib,qt_wm_state,qt-mt)
+endef
+
+# config conditionals
+includes := SOUNDCARD_H SYS_SOUNDCARD_H SUN_AUDIOIO_H
+inc_cflags := $(call ac_inc_cflags,$(includes))
+CFLAGS += $(inc_cflags)
+
+ifeq ($(HAVE_LIBQT_MT),yes)
+QTLIB=-lqt-mt
+else
+QTLIB=-lqt
+endif
+
+
+##########################################################################
+# my targets
+
+# krecord
+TARGET := krecord
+OBJS := krecord.o sound.o fft.o level.o buffer.o soundfft.o \
+ oss.o sunaudio.o
+SRCS := krecord.cpp sound.cpp fft.cpp level.cpp buffer.cpp soundfft.c \
+ oss.cpp sunaudio.cpp
+MOCS := krecord.moc sound.moc fft.moc level.moc buffer.moc \
+ oss.moc sunaudio.moc
+
+CXXFLAGS := $(CFLAGS)
+LDLIBS += -lkdeui -lkdecore $(QTLIB) -lXext -lXmu -lX11
+
+# locales
+PO := $(wildcard po/*.po)
+MO := $(subst .po,.mo,$(PO))
+MERGE := $(subst .po,.pox,$(PO))
+LANGS := $(patsubst po/%.po,install-lang-%,$(PO))
+
+build: $(TARGET) $(MO)
+
+$(TARGET): $(OBJS)
+
+install: install-bin $(LANGS)
+
+install-bin: $(TARGET)
+ $(INSTALL_DIR) $(bindir)
+ $(INSTALL_BINARY) $(TARGET) $(bindir)
+ $(INSTALL_DIR) $(appsdir)
+ $(INSTALL_DATA) krecord.kdelnk $(appsdir)
+ $(INSTALL_DIR) $(datadir)/toolbar
+ $(INSTALL_DATA) img/*.png $(datadir)/toolbar
+ $(INSTALL_DIR) $(htmldir)
+ $(INSTALL_DATA) index.html $(htmldir)
+
+install-lang-%: po/%.mo
+ $(INSTALL_DIR) $(locdir)/$*/LC_MESSAGES
+ $(INSTALL_DATA) $< $(locdir)/$*/LC_MESSAGES/krecord.mo
+
+clean:
+ rm -f *.o $(depfiles)
+ rm -f *.bak *~ *% core* "#*"
+ rm -f po/*~ po/*.mo
+
+realclean distclean:: clean
+ rm -f $(TARGET) $(MOCS) Make.config
+
+po/krecord.pot: $(SRCS)
+ touch messages.po
+ xgettext -sj --keyword=i18n $(SRCS)
+ mv messages.po $@
+
+locale: po/krecord.pot $(MERGE)
+
+
+##########################################################################
+# some rules
+
+%.pox : %.po po/krecord.pot
+ msgmerge $< po/krecord.pot > $@
+
+buffer.o: buffer.moc
+fft.o: fft.moc
+krecord.o: krecord.moc
+level.o: level.moc
+oss.o: oss.moc
+sound.o: sound.moc
+sunaudio.o: sunaudio.moc
+
+include mk/Compile.mk
+include mk/Maintainer.mk
+-include $(depfiles)
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..0d1ba36
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,59 @@
+
+howto compile and install this package
+======================================
+
+
+really short install instructions
+---------------------------------
+
+ $ make
+ $ su -c "make install"
+
+
+
+the more detailed version
+-------------------------
+
+Make sure you use GNU make. The file name "GNUmakefile" isn't a joke,
+this package really requires GNU make.
+
+As first step make will do some config checks on your system and write
+the results to Make.config. If you want to have a look at Make.config
+before the actual build starts you can run this step separately using
+"make config".
+
+The Makefiles use the usual GNU-ish Makefile conventions for variable
+names and default values, i.e. prefix=/usr/local, ...
+
+The values for some frequently adapted variables are initialized from
+the enviroment. Thus you can change the defaults simply by setting
+environment variables:
+
+ $ prefix="/usr"
+ $ CFLAGS="-O3 -mcpu=i686"
+ $ export prefix CFLAGS
+
+Almost any variable can be overridden on the make command line. It is
+often used this way to install into some buildroot for packaging ...
+
+ $ su -c "make DESTDIR=/tmp/buildroot install"
+
+... but it works for most other variables equally well. There are
+some exceptions through, it usually does _not_ work for CFLAGS for
+example.
+
+Try "make verbose=yes" if you want to see the complete command lines
+executed by make instead of the short messages (for trouble shooting,
+because you like this way, for whatever reason ...). This also makes
+the config checks performed by "make config" more verbose.
+
+If you don't trust my Makefiles you can run "make -n install" to see
+what "make install" would do on your system. It will produce
+human-readable output (unlike automake ...).
+
+Have fun,
+
+ Gerd
+
+--
+Gerd Knorr <kraxel@bytesex.org>
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();
+ }
+}
diff --git a/buffer.h b/buffer.h
new file mode 100644
index 0000000..5bf635e
--- /dev/null
+++ b/buffer.h
@@ -0,0 +1,192 @@
+#ifndef BUFFER_H
+#define BUFFER_H
+
+#include <inttypes.h>
+#include <qobject.h>
+#include <qlistbox.h>
+
+#include "sound.h"
+
+/* ---------------------------------------------------------------------- */
+/* *.wav I/O stolen from cdda2wav */
+
+/* Copyright (C) by Heiko Eissfeldt */
+
+typedef uint8_t BYTE;
+typedef uint16_t WORD;
+typedef uint32_t DWORD;
+typedef uint32_t FOURCC; /* a four character code */
+
+/* flags for 'wFormatTag' field of WAVEFORMAT */
+#define WAVE_FORMAT_PCM 1
+
+/* MMIO macros */
+#define mmioFOURCC(ch0, ch1, ch2, ch3) \
+ ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
+ ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24))
+
+#define FOURCC_RIFF mmioFOURCC ('R', 'I', 'F', 'F')
+#define FOURCC_LIST mmioFOURCC ('L', 'I', 'S', 'T')
+#define FOURCC_WAVE mmioFOURCC ('W', 'A', 'V', 'E')
+#define FOURCC_FMT mmioFOURCC ('f', 'm', 't', ' ')
+#define FOURCC_DATA mmioFOURCC ('d', 'a', 't', 'a')
+
+typedef struct CHUNKHDR {
+ FOURCC ckid; /* chunk ID */
+ DWORD dwSize; /* chunk size */
+} CHUNKHDR;
+
+/* simplified Header for standard WAV files */
+typedef struct WAVEHDR {
+ CHUNKHDR chkRiff;
+ FOURCC fccWave;
+ CHUNKHDR chkFmt;
+ WORD wFormatTag; /* format type */
+ WORD nChannels; /* number of channels (i.e. mono, stereo, etc.) */
+ DWORD nSamplesPerSec; /* sample rate */
+ DWORD nAvgBytesPerSec; /* for buffer estimation */
+ WORD nBlockAlign; /* block size of data */
+ WORD wBitsPerSample;
+ CHUNKHDR chkData;
+} WAVEHDR;
+
+#define IS_STD_WAV_HEADER(waveHdr) ( \
+ waveHdr.chkRiff.ckid == FOURCC_RIFF && \
+ waveHdr.fccWave == FOURCC_WAVE && \
+ waveHdr.chkFmt.ckid == FOURCC_FMT && \
+ waveHdr.chkData.ckid == FOURCC_DATA && \
+ waveHdr.wFormatTag == WAVE_FORMAT_PCM)
+
+/* ---------------------------------------------------------------------- */
+
+class AudioBuffer {
+
+protected:
+ struct SOUNDPARAMS params;
+ int size;
+ int busy;
+ int position;
+
+public:
+ AudioBuffer();
+ virtual ~AudioBuffer();
+
+ int clippedsamples;
+ int is_busy();
+ void balloc(void);
+ void bfree(void);
+
+ virtual int start_write(struct SOUNDPARAMS *p); /* write */
+ virtual void stop_write(); /* can flush etc. */
+
+ virtual struct SOUNDPARAMS *get_params(); /* after recording or */
+ virtual int get_size(); /* for file readling */
+ virtual char *name();
+
+ virtual void *read_audio(int len); /* no comment ... */
+ virtual int write_audio(int len, void *data);
+ virtual int seek(int pos);
+ virtual int tell();
+};
+
+class RAMBuffer : public AudioBuffer {
+private:
+ char **buffers;
+ int bufcount;
+ char bufname[32];
+
+public:
+ RAMBuffer();
+ virtual ~RAMBuffer();
+
+ virtual int start_write(struct SOUNDPARAMS *p);
+ virtual void stop_write();
+
+ virtual struct SOUNDPARAMS *get_params();
+ virtual int get_size();
+ virtual char *name();
+
+ virtual void *read_audio(int len);
+ virtual int write_audio(int len, void *data);
+ virtual int seek(int pos);
+ virtual int tell();
+};
+
+class FileBuffer : public AudioBuffer {
+private:
+ int fd,ro;
+ int offset,bstart,bstop;
+ char filename[256];
+ WAVEHDR fileheader;
+ char buffer[65536];
+
+ void init_header();
+ int parse_header();
+
+public:
+ FileBuffer();
+ virtual ~FileBuffer();
+
+ int attach(const char *file);
+
+ virtual int start_write(struct SOUNDPARAMS *p);
+ virtual void stop_write();
+
+ virtual struct SOUNDPARAMS *get_params();
+ virtual int get_size();
+ virtual char *name();
+
+ virtual void *read_audio(int len);
+ virtual int write_audio(int len, void *data);
+ virtual int seek(int pos);
+ virtual int tell();
+};
+
+/* -- functions -------------------------------------------------------- */
+
+class BufferList : public QObject {
+ Q_OBJECT;
+
+public:
+ BufferList(QListBox *l, Soundcard *c);
+ int add_filebuffer(const char *filename);
+ void save_buf(const char *filename);
+
+private:
+ QListBox *listbox;
+ Soundcard *card;
+ int mon,level,wait;
+
+ struct SOUNDPARAMS params;
+ AudioBuffer **buffers;
+ int count,brecord,bplayback;
+ int new_buffer_count;
+
+ int add_rambuffer();
+ void add_buffer(AudioBuffer *buf);
+ void label_buffer(int buf);
+ void del_buffer(int buf);
+ void istop();
+ void set_pos_size(const char *start, int buf);
+
+public slots:
+ void new_params(struct SOUNDPARAMS *params);
+ void new_data(void *data);
+ void post_data(void *data);
+
+ void new_ram();
+ void del_buf();
+ void set_level(int l);
+ void monitor();
+ void record();
+ void stop();
+ void play();
+ void forward();
+ void backward();
+ void next_buffer();
+
+signals:
+ void status(const char *text);
+};
+
+#endif
diff --git a/byteorder.h b/byteorder.h
new file mode 100644
index 0000000..436c320
--- /dev/null
+++ b/byteorder.h
@@ -0,0 +1,49 @@
+
+#if sun
+
+#define LITTLE_ENDIAN 1234
+#define BIG_ENDIAN 4321
+#define PDP_ENDIAN 3412
+
+#include <sys/isa_defs.h>
+
+#if defined(_LITTLE_ENDIAN)
+#define BYTE_ORDER LITTLE_ENDIAN
+#elif defined(_BIG_ENDIAN)
+#define BYTE_ORDER BIG_ENDIAN
+#endif
+
+#else
+
+/* linux */
+#include <endian.h>
+
+#endif
+
+
+/* ---------------------------------------------------------------------- */
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+
+# define cpu_to_le32(x) (x)
+# define cpu_to_le16(x) (x)
+# define le32_to_cpu(x) (x)
+# define le16_to_cpu(x) (x)
+
+#elif BYTE_ORDER == BIG_ENDIAN
+
+# define cpu_to_le32(x) (\
+ ((x & 0x000000FF) << 24) | \
+ ((x & 0x0000FF00) << 8) | \
+ ((x & 0x00FF0000) >> 8) | \
+ ((x & 0xFF000000) >> 24))
+# define le32_to_cpu(x) cpu_to_le32(x)
+
+# define cpu_to_le16(x) (((x & 0x00FF) << 8) | (x >> 8))
+# define le16_to_cpu(x) cpu_to_le16(x)
+
+#else
+
+# error Unknow byte order type
+
+#endif
diff --git a/fft.cpp b/fft.cpp
new file mode 100644
index 0000000..525a584
--- /dev/null
+++ b/fft.cpp
@@ -0,0 +1,218 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <math.h>
+
+#include <qwidget.h>
+
+#include "sound.h"
+#include "fft.moc"
+
+#include <X11/Xlib.h>
+
+extern "C" {
+ #include "soundfft.h"
+}
+
+#define FFT_MAX 8192
+#define FFT_LEFT 100
+#define FFT_RIGHT 10000
+#define DB_MAX 0.0
+#define DB_MIN -100.0
+
+/* ---------------------------------------------------------------------- */
+
+FFTWindow::FFTWindow(QWidget *parent, char *name) : QWidget(parent,name,0)
+{
+ XColor color,dummy;
+ Colormap map = DefaultColormapOfScreen
+ (DefaultScreenOfDisplay(x11Display()));
+
+ gc = XCreateGC(x11Display(),winId(),0,NULL);
+ XAllocNamedColor(x11Display(),map,"white",&color,&dummy);
+ back = color.pixel;
+ XAllocNamedColor(x11Display(),map,"red",&color,&dummy);
+ fore = color.pixel;
+
+ /* flag: no init so far... */
+ channels = 0;
+ logmap = NULL;
+ ylog = false;
+}
+
+void
+FFTWindow::make_logmap()
+{
+ int i,w,h;
+ float freq,up;
+
+ w = width();
+ h = height();
+
+ if (!w || !h || !channels)
+ return;
+
+ if (logmap) {
+ free(logmap);
+ free(segments);
+ free(buffer);
+ EndFFT();
+ }
+ logmap = (int*) malloc((w+1)*sizeof(int));
+ segments = (XSegment*)malloc((w)*sizeof(XSegment));
+ buffer = (short*)malloc(fft_size*sizeof(short)*channels);
+ InitializeFFT(fft_size);
+
+ up = log(FFT_RIGHT/FFT_LEFT)/log(2);
+ for (i = 0; i <= w; i++) {
+ freq = pow(2,up*i/w)* FFT_LEFT;
+ logmap[i] = (int)(fft_size*freq/rate);
+ }
+ for (i = 0; i < w; i++) {
+ segments[i].x1 = i;
+ segments[i].x2 = i;
+ segments[i].y2 = h;
+ }
+}
+
+void
+FFTWindow::calculate(unsigned char *data)
+{
+ int i,j;
+
+ if (NULL == logmap) {
+ fprintf(stderr,"Oops: fft no logmap (yet)\n");
+ return;
+ }
+
+ switch (afmt) {
+ case FMT_8BIT:
+ for (i = 0; i < audio_size; i++)
+ buffer[i] = (data[i] ^ 0x80) << 8;
+ break;
+#if 0
+ case AFMT_S8:
+ for (i = 0; i < audio_size; i++)
+ buffer[i] = data[i] << 8;
+ break;
+#endif
+ case FMT_16BIT:
+ memcpy(buffer,data,audio_size);
+#if 0
+ /* byte swap */
+ for (i = 0; i < fft_size; i++)
+ buffer[i] =
+ ((buffer[i] & 0xff00) >> 8) |
+ ((buffer[i] & 0x00ff) << 8);
+ /* unsigned -> signed */
+ if (afmt == AFMT_U16_LE || afmt == AFMT_U16_BE)
+ for (i = 0; i < fft_size; i++)
+ buffer[i] ^= 0x8000;
+#endif
+ break;
+ default:
+ fprintf(stderr,"oops(fft): unknown sound format\n");
+ exit(1);
+ }
+
+ for (i = 0, lmax = 0; i < fft_size; i++)
+ if ((j = abs((signed short)buffer[i])) > lmax)
+ lmax = j;
+
+ if (channels == 2) {
+ for (i = 0, j = 0; i < fft_size; i++, j+=2)
+ buffer[i] = (buffer[j]+buffer[j+1])>>1;
+ }
+ RealFFT(buffer);
+}
+
+void
+FFTWindow::drawhist()
+{
+ XGCValues gcval;
+
+ int re,im,ab;
+ int i,j,max,w,h;
+ int len = fft_size>>1;
+
+ w = width();
+ h = height();
+ for (i = 0; i < w; i++) {
+ max = 0;
+ j = logmap[i];
+ do {
+ if (j >= len)
+ break;
+ re = buffer[BitReversed[j]];
+ im = buffer[BitReversed[j]+1];
+ ab = re*re+im*im;
+ if (ab > max)
+ max = ab;
+
+ j++;
+ } while (j < logmap[i+1]);
+ if (ylog) {
+ float dBVal = 20 * log10( sqrt(max)/FFT_MAX );
+ max = (int)( h * ( dBVal-DB_MIN )/( DB_MAX-DB_MIN ));
+ } else {
+ max = (int)(sqrt(max) * h / FFT_MAX);
+ }
+ segments[i].y1 = h-max;
+ }
+
+ gcval.foreground = back;
+ XChangeGC(x11Display(),gc,GCForeground,&gcval);
+ XFillRectangle(x11Display(),winId(),gc,0,0,width(),height());
+
+ gcval.foreground = fore;
+ XChangeGC(x11Display(),gc,GCForeground,&gcval);
+ XDrawSegments(x11Display(),winId(),gc,segments,w);
+ if (ylog) {
+ float dBVal = 20 * log10( (double)lmax/32768 );
+ max = h - (int)( h * ( dBVal-DB_MIN )/( DB_MAX-DB_MIN ));
+ } else {
+ max = height() - lmax*height()/32768;
+ }
+ XDrawLine(x11Display(),winId(),gc,0,max,width(),max);
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+FFTWindow::resizeEvent(QResizeEvent *event)
+{
+ make_logmap();
+}
+
+void
+FFTWindow::new_params(struct SOUNDPARAMS *p)
+{
+ afmt = p->format;
+ channels = p->channels;
+ rate = p->rate;
+ audio_size = p->blocksize;
+
+ fft_size = audio_size/channels/2;
+ if (afmt != FMT_16BIT)
+ fft_size *= 2;
+
+ make_logmap();
+}
+
+void
+FFTWindow::new_data(void *data)
+{
+ if (!channels || !isVisible())
+ return;
+
+ calculate((unsigned char*)data);
+ drawhist();
+}
+
+void
+FFTWindow::set_ylog(int linear)
+{
+ ylog = (linear==0);
+ drawhist();
+}
diff --git a/fft.h b/fft.h
new file mode 100644
index 0000000..d81341f
--- /dev/null
+++ b/fft.h
@@ -0,0 +1,40 @@
+#ifndef FFT_H
+#define FFT_H
+
+#include <qwidget.h>
+#include <X11/Xlib.h>
+
+/* ------------------------------------------------------------------------ */
+
+class FFTWindow : public QWidget
+{
+ Q_OBJECT;
+
+public:
+ FFTWindow(QWidget *parent, char *name);
+
+public slots:
+ void new_params(struct SOUNDPARAMS *params);
+ void new_data(void *data);
+ void set_ylog(int linear);
+
+protected:
+ void resizeEvent(QResizeEvent *event);
+
+private:
+ unsigned long fore,back;
+ GC gc;
+ int *logmap;
+ XSegment *segments;
+ short *buffer;
+
+ int afmt,channels,rate;
+ int audio_size, fft_size, lmax;
+ bool ylog;
+
+ void make_logmap();
+ void calculate(unsigned char *data);
+ void drawhist();
+};
+
+#endif
diff --git a/img/1rightarrow.png b/img/1rightarrow.png
new file mode 100755
index 0000000..fbac1f1
--- /dev/null
+++ b/img/1rightarrow.png
Binary files differ
diff --git a/img/2leftarrow.png b/img/2leftarrow.png
new file mode 100755
index 0000000..629c4ff
--- /dev/null
+++ b/img/2leftarrow.png
Binary files differ
diff --git a/img/2rightarrow.png b/img/2rightarrow.png
new file mode 100755
index 0000000..91a01ff
--- /dev/null
+++ b/img/2rightarrow.png
Binary files differ
diff --git a/img/freq.png b/img/freq.png
new file mode 100755
index 0000000..5833bb5
--- /dev/null
+++ b/img/freq.png
Binary files differ
diff --git a/img/level.png b/img/level.png
new file mode 100755
index 0000000..e7357fa
--- /dev/null
+++ b/img/level.png
Binary files differ
diff --git a/img/line_monitor.png b/img/line_monitor.png
new file mode 100755
index 0000000..d1838c4
--- /dev/null
+++ b/img/line_monitor.png
Binary files differ
diff --git a/img/mrecord.png b/img/mrecord.png
new file mode 100755
index 0000000..6ea6809
--- /dev/null
+++ b/img/mrecord.png
Binary files differ
diff --git a/img/player_stop.png b/img/player_stop.png
new file mode 100755
index 0000000..5ed11cd
--- /dev/null
+++ b/img/player_stop.png
Binary files differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..5e9b03b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,118 @@
+<html>
+<head>
+<title>krecord help</title>
+</head>
+<body bgcolor=white text=black>
+
+<h1>Help for krecord</h1>
+
+krecord is a easy-to-use sound recorder for KDE, it can record to (and
+playback from) memory and *.wav files. Should'nt be a problem to
+record huge files. Well, unless you run out of memory...
+<p>
+Recording and playback is basically finished, fine-tuned and debugged for
+Linux/i386 systems now. There are some i18n and portability issues.
+Other OSS/little-endian boxes should work fine too (but this is untested),
+OSS/big-endian should work with 8-bit sound.
+<p>
+I dropped my plans to add more functions and entered the
+BugfixingAndMaintaining Mode. For editing check out the other
+programs, there is kwav for example.
+
+<h3>Description</h3>
+
+mostly TODO, currently only the most important stuff is listed.
+<p>
+The main part of the window is just a list of the buffers you have.
+Empty after startup. krecord knows two sorts of buffers:
+<dl>
+<dt>memory buffers
+<dd>these are kept in memory, if you want to keep the data there, you
+have to save the buffer to a file.
+These are listed as &quot;buffer #nr&quot;.
+<dt>file buffers
+<dd>these are attached to a file and krecord reads and writes directly
+from/to the file. These are listed with the filename.
+</dl>
+File buffers are useful if you want to record huge sound files (with size >
+RAM). But they are more sensitive to background activity,
+i.e. recording overruns are more likely.
+<p>
+recording/playback starts allways at the beginning of the
+buffer, there is no position scale.
+<p>
+The status line holds (from left to right): status / sample rate /
+channels / audio format / latency. status might be:
+<dl compact>
+<dt>idle
+<dt>playback
+<dt>recording
+<dd>I think these are clear...
+<dt>monitor
+<dd>reads data from the souncard and processes them. You can use the
+freq spectrum window or input level window (or both :-) to see what
+comes in. You can adjust the record level then. It is nice for
+trouble-shooting too. If no data arrives, you probably have to set
+the right recording source with a mixer program, kmix for example.
+There is a menu entry in the options menu to start up kmix...
+<dt>waiting
+<dd>krecord waits for a signal higher than the record trigger level.
+record trigger level is specified in percent. 5% gives good results
+most of the time for me, but YMMV depending on quality and volume of
+the input signal.
+</dl>
+<p>
+The freq spectrum displays the frequencies from 100Hz to 10kHz. The
+x-axis has a logaritmic scale - 1kHz is in the middle. y-axis is
+linear, the horizontal red line ist the maximum level.
+<p>
+There are a few handy keyboard shortcuts:
+<dl compact>
+<dt>up/down
+<dd>walk in the buffer list
+<dt>Return
+<dd>start playback
+<dt>'R'
+<dd>start recording
+<dt>Escape
+<dd>stop record/playback
+<dt>'N', Space
+<dd>starts a new file buffer (named song&lt;nr&gt;.wav). This
+is useful for recording your old LP's (if you want to burn them to
+CD's): Just start krecord, your LP, and press 'N' every time you want
+to start a new track. Burn with &quot;<tt>cdrecord -audio
+song*.wav</tt>&quot;.
+</dl>
+
+<h3>Known Problems</h3>
+
+<ul>
+<li>I have trouble with recording overruns sometimes :-(
+<li>The freq spectrum/input level is'nt in sync for playback - it
+does'nt take in account the delay due to the sound driver buffering.
+<li>Supports OSS only. Yes, there already is a sunaudio.cpp file, but
+this is currently a dummy only...<br>
+support for big-endian machines needs some work too.
+</ul>
+
+<h3>TODO List</h3>
+
+<ul>
+<li>more i18n
+<li>portability
+</ul>
+
+<h3>Credits</h3>
+
+<ul>
+<li>Florian Kolbe &lt;Florian.Kolbe@in-gmbh.de&gt; (input level window)
+<li>Stéphane Gourichon &lt;gouricho@poleia.lip6.fr&gt; (input level patches)
+<li>Thomas Strehl &lt;tstrehl@suse.de&gt; (KDE2 port)
+</ul>
+
+<hr noshade>
+<address>Gerd Knorr
+&lt;<a href="mailto:kraxel@goldbach.in-berlin.de">kraxel@goldbach.in-berlin.de</a>&gt;
+</address>
+</body>
+</html>
diff --git a/krecord.cpp b/krecord.cpp
new file mode 100644
index 0000000..fc31bbe
--- /dev/null
+++ b/krecord.cpp
@@ -0,0 +1,658 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#include <iostream>
+
+#include <qlabel.h>
+#include <qfiledialog.h>
+#include <qdragobject.h>
+#include <qtooltip.h>
+#include <qlayout.h>
+#include <qbuttongroup.h>
+#include <qhbuttongroup.h>
+#include <qradiobutton.h>
+#include <qaccel.h>
+#include <qmenubar.h>
+#include <qpopupmenu.h>
+
+#include <kapp.h>
+#include <kuniqueapp.h>
+#include <kmessagebox.h>
+#include <kmenubar.h>
+#include <kstatusbar.h>
+#include <kmainwindow.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kprocess.h>
+#include <kaboutdata.h>
+#include <kcmdlineargs.h>
+#include <khelpmenu.h>
+#include <kmenubar.h>
+#include <kpopupmenu.h>
+
+#include "sound.h"
+#include "fft.h"
+#include "level.h"
+#include "buffer.h"
+#include "krecord.moc"
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xmu/WinUtil.h> /* for XmuClientWindow() */
+
+#define STAT_LATENCY 1
+#define STAT_RATE 2
+#define STAT_CHANNELS 3
+#define STAT_FORMAT 4
+#define STAT_MISC 5
+
+KApplication *globalKapp;
+KIconLoader *globalKIL;
+KLocale *globalKlocale;
+const char *device = NULL;
+
+/* ------------------------------------------------------------------------ */
+
+int main(int argc, char **argv)
+{
+ static KCmdLineOptions options[] = {
+ { "d", 0, 0 },
+ { "device <dev>", "dsp sound device", "/dev/dsp" },
+ { "+[files]", "optional wav files", 0 },
+ KCmdLineLastOption
+ };
+
+ KCmdLineArgs *args;
+ KRecord *krecord;
+ int i;
+
+ KAboutData aboutData("krecord", "KRecord", KRECORD_VERSION,
+ "KDE sound recorder",
+ KAboutData::License_GPL,
+ "(c) 1997-2003 Gerd Knorr <kraxel@bytesex.org>\n",
+ 0,
+ "http://bytesex.org/krecord.html");
+ aboutData.addAuthor("Gerd Knorr","Maintainer & Developer",
+ "kraxel@bytesex.org", "http://bytesex.org");
+ aboutData.addCredit("Thomas Strehl", "KDE2 port", "tstrehl@suse.de");
+
+ KCmdLineArgs::init(argc, argv, &aboutData);
+ KCmdLineArgs::addCmdLineOptions(options);
+
+ globalKapp = new KApplication("krecord");
+ globalKIL = KGlobal::iconLoader();
+ globalKlocale = KGlobal::locale();
+
+ args = KCmdLineArgs::parsedArgs();
+ if (args->isSet("device"))
+ device = args->getOption("device");
+
+ krecord = new KRecord();
+ for (i = 0; i < args->count(); i++) {
+ fprintf(stderr,"file: %s\n",args->arg(i));
+ krecord->blist->add_filebuffer(args->arg(i));
+ }
+ args->clear();
+
+ return globalKapp->exec();
+}
+
+/* ------------------------------------------------------------------------ */
+
+KRecord::KRecord() : KMainWindow(0,"main")
+{
+ int i = -1;
+
+ soundcard = new Soundcard(device);
+ soundopts = new SoundOptions(soundcard,"soundopts");
+ kfft = new KFFT(soundcard);
+ klevel = new KLevel(soundcard);
+ listwidget = new QListBox(this,"bufferlist");
+ blist = new BufferList(listwidget,soundcard);
+ fdialog = new QFileDialog(NULL,"*.wav",NULL,"fdialog",TRUE);
+ accel = new QAccel(this);
+
+ globalKapp->setMainWidget(this);
+ setCentralWidget(listwidget);
+ setAcceptDrops(TRUE);
+
+ create_menu();
+ create_toolbar();
+ create_soundbar();
+ create_statusline();
+ soundopts->set_soundparam(44100,2,FMT_16BIT,0);
+
+ accel->connectItem(accel->insertItem(Key_Enter), blist,SLOT(play()));
+ accel->connectItem(accel->insertItem(Key_Return), blist,SLOT(play()));
+ accel->connectItem(accel->insertItem(Key_Escape), blist,SLOT(stop()));
+ accel->connectItem(accel->insertItem(Key_Delete), blist,SLOT(del_buf()));
+ accel->connectItem(accel->insertItem(Key_R), blist,SLOT(record()));
+ accel->connectItem(accel->insertItem(Key_N),
+ blist,SLOT(next_buffer()));
+ accel->connectItem(accel->insertItem(Key_Space),
+ blist,SLOT(next_buffer()));
+
+ connect(soundcard,SIGNAL(newparams(struct SOUNDPARAMS*)),
+ this, SLOT(update_statusline(struct SOUNDPARAMS*)));
+ connect(blist,SIGNAL(status(const char*)),
+ this, SLOT(update_statusline(const char*)));
+ connect(soundopts,SIGNAL(set_level(int)),
+ blist, SLOT(set_level(int)));
+
+ /* session management */
+ if (globalKapp->isRestored()) {
+ for (i = 1; canBeRestored(i); i++)
+ if (0 == strcmp(classNameOfToplevel(i),"KRecord"))
+ break;
+ if (!canBeRestored(i))
+ i = -1;
+ }
+ if (i > 0) {
+ restore(i);
+ } else {
+ resize(400,250);
+ show();
+ }
+
+ blist->monitor();
+}
+
+KRecord::~KRecord()
+{
+ delete opt_menu;
+ delete file_menu;
+
+ delete toolbar;
+ delete soundbar;
+ delete statusline;
+
+ delete blist;
+ delete listwidget;
+
+ delete fdialog;
+ delete kfft;
+ delete soundopts;
+ delete soundcard;
+}
+
+void
+KRecord::create_menu()
+{
+ file_menu = new QPopupMenu;
+ file_menu->insertItem( i18n("&New memory buffer"),
+ blist, SLOT(new_ram()));
+ file_menu->insertItem( i18n("New &file buffer..."),
+ this, SLOT(new_file()));
+ file_menu->insertItem( i18n("&Save buffer as..."),
+ this, SLOT(save_as()));
+ file_menu->insertSeparator();
+ file_menu->insertItem( i18n("&Delete buffer"),
+ blist, SLOT(del_buf()));
+ file_menu->insertSeparator();
+ file_menu->insertItem( i18n("&Quit"),
+ this, SLOT(quit_cb()), CTRL+Key_Q);
+
+ opt_menu = new QPopupMenu;
+ opt_menu->insertItem( i18n("&Sound Options..."),
+ this, SLOT(record_options()));
+ opt_menu->insertItem( i18n("&Freq Spectrum..."),
+ kfft, SLOT(showit()));
+ opt_menu->insertItem( i18n("&Input Level..."),
+ klevel, SLOT(showit()));
+ opt_menu->insertItem( i18n("Run &Mixer"),
+ this, SLOT(exec_mixer()));
+ opt_menu->insertSeparator();
+ tb_mid = opt_menu->insertItem( i18n("Hide &Toolbar"),
+ this,SLOT(tb_toggle()));
+ sl_mid = opt_menu->insertItem( i18n("Hide Status&line"),
+ this,SLOT(sl_toggle()));
+
+ KPopupMenu *help = helpMenu();
+
+ menuBar()->insertItem( i18n("&File"), file_menu);
+ menuBar()->insertItem( i18n("&Options"), opt_menu);
+ menuBar()->insertItem( i18n("&Help"), help);
+}
+
+void
+KRecord::create_toolbar()
+{
+ toolbar = new KToolBar(this,"Toolbar");
+ KIconLoader *loader = KGlobal::iconLoader();
+ QPixmap pixmap;
+
+ pixmap = loader->loadIcon("filenew",KIcon::Toolbar);
+ toolbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), blist, SLOT(new_ram()), TRUE,
+ i18n("New memory buffer"));
+
+ pixmap = loader->loadIcon("filesave",KIcon::Toolbar);
+ toolbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), this, SLOT(save_as()), TRUE,
+ i18n("Save buffer"));
+
+ toolbar->insertSeparator();
+
+ pixmap = loader->loadIcon("freq",KIcon::Toolbar);
+ toolbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), kfft, SLOT(showit()), TRUE,
+ i18n("Freq Spectrum"));
+
+ pixmap = loader->loadIcon("level",KIcon::Toolbar);
+ toolbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), klevel, SLOT(showit()), TRUE,
+ i18n("Input Level"));
+
+ toolbar->insertSeparator();
+
+#if 0
+ pixmap = loader->loadIcon("help",KIcon::Toolbar,10);
+ toolbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), this, SLOT(help_cb()), TRUE,
+ i18n("Help"));
+#endif
+
+ toolbar->insertSeparator();
+ pixmap = loader->loadIcon("exit",KIcon::Toolbar);
+ toolbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), this, SLOT(quit_cb()), TRUE,
+ i18n("Quit"));
+
+ toolbar->setBarPos(KToolBar::Top);
+ addToolBar(toolbar);
+}
+
+void
+KRecord::create_soundbar()
+{
+ soundbar = new KToolBar(this,"Soundbar");
+ KIconLoader *loader = KGlobal::iconLoader();
+ QPixmap pixmap;
+
+ pixmap = loader->loadIcon("forward",KIcon::Toolbar,10);
+ soundbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), blist, SLOT(next_buffer()), TRUE,
+ i18n("Switch to new buffer"));
+
+ pixmap = loader->loadIcon("mrecord",KIcon::Toolbar);
+ soundbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), blist, SLOT(record()), TRUE,
+ i18n("Start Record"));
+
+ pixmap = loader->loadIcon("player_stop",KIcon::Toolbar);
+ soundbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), blist, SLOT(stop()), TRUE,
+ i18n("Stop Record/Playback"));
+
+ pixmap = loader->loadIcon("1rightarrow",KIcon::Toolbar);
+ soundbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), blist, SLOT(play()), TRUE,
+ i18n("Start Playback"));
+
+ pixmap = loader->loadIcon("2leftarrow",KIcon::Toolbar);
+ soundbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), blist, SLOT(backward()), TRUE,
+ i18n("Back"));
+
+ pixmap = loader->loadIcon("2rightarrow",KIcon::Toolbar);
+ soundbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), blist, SLOT(forward()), TRUE,
+ i18n("Forward"));
+
+ soundbar->insertSeparator();
+ pixmap = loader->loadIcon("line_monitor",KIcon::Toolbar);
+ soundbar->insertButton
+ (pixmap, 0, SIGNAL(clicked()), blist, SLOT(monitor()), TRUE,
+ i18n("Turn on/off monitor"));
+
+ soundbar->setBarPos(KToolBar::Top);
+ addToolBar(soundbar);
+}
+
+void
+KRecord::create_statusline()
+{
+ statusline = new KStatusBar(this);
+
+ statusline->insertItem("-", STAT_MISC, 1);
+ statusline->setItemAlignment (STAT_MISC, AlignLeft);
+ statusline->insertFixedItem("9999999", STAT_RATE);
+ statusline->insertFixedItem("xxxxxxx", STAT_CHANNELS);
+ statusline->insertFixedItem("xxxxxxxxxx", STAT_FORMAT);
+ statusline->insertFixedItem("999 ms", STAT_LATENCY);
+}
+
+void
+KRecord::update_statusline(struct SOUNDPARAMS *p)
+{
+ char text[32];
+
+ statusline->changeItem((1==p->channels) ? "mono":"stereo", STAT_CHANNELS);
+ sprintf(text,"%d",p->rate);
+ statusline->changeItem(text,STAT_RATE);
+ sprintf(text,"%d ms",p->latency);
+ statusline->changeItem(text,STAT_LATENCY);
+ statusline->changeItem(sndfmt2str(p->format),STAT_FORMAT);
+}
+
+void
+KRecord::update_statusline(const char *text)
+{
+ statusline->changeItem(text,STAT_MISC);
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+void KRecord::new_file()
+{
+ QString filename;
+
+ if (NULL == (filename = fdialog->getSaveFileName
+ (NULL,"*.wav",NULL,"fdialog")))
+ return;
+ blist->add_filebuffer(filename);
+}
+
+void KRecord::save_as()
+{
+ QString filename;
+
+ if (NULL == (filename = fdialog->getSaveFileName
+ (NULL,"*.wav",NULL,"fdialog")))
+ return;
+ blist->save_buf(filename);
+}
+
+void KRecord::quit_cb()
+{
+ blist->stop();
+ delete this;
+ globalKapp->quit();
+}
+
+void KRecord::record_options()
+{
+ soundopts->show();
+}
+
+void KRecord::exec_mixer()
+{
+ KProcess *kmix;
+ kmix = new KProcess;
+ *kmix << "kmix";
+ kmix->start(KProcess::DontCare);
+}
+
+void KRecord::tb_toggle()
+{
+ if (toolBar("Toolbar")->isVisible()) {
+ toolBar("Toolbar")->enable(KToolBar::Hide);
+ opt_menu->changeItem(i18n("Show &Toolbar"), tb_mid);
+ } else {
+ toolBar("Toolbar")->enable(KToolBar::Show);
+ opt_menu->changeItem(i18n("Hide &Toolbar"), tb_mid);
+ }
+}
+
+void KRecord::sl_toggle()
+{
+ if (statusBar()->isVisible()) {
+ statusBar()->hide();
+ opt_menu->changeItem(i18n("Show Status&line"), sl_mid);
+ } else {
+ statusBar()->show();
+ opt_menu->changeItem(i18n("Hide Status&line"), sl_mid);
+ }
+}
+
+void KRecord::dropEvent(QDropEvent *de) // something has been dropped
+{
+ QStrList strlist;
+ QUriDrag::decode(de,strlist);
+ QString *url = new QString(strlist.first());
+ const char *h;
+
+ fprintf(stderr,"dropEvent\n");
+ while ((const char*)*url) {
+ h = (const char*)*url;
+ if (0 == strncmp(h,"file:",5))
+ blist->add_filebuffer(h+5);
+ delete url;
+ url = new QString(strlist.next());
+ }
+}
+
+void KRecord::dragEnterEvent(QDragEnterEvent* event)
+{
+ event->accept(QTextDrag::canDecode(event) ||
+ QImageDrag::canDecode(event));
+}
+
+/* ------------------------------------------------------------------------ */
+
+KFFT::KFFT(Soundcard *card) : KMainWindow(0,"fft",WType_TopLevel)
+{
+ int i = -1;
+
+ QWidget* centralWidget = new QWidget(this);
+ setCentralWidget(centralWidget);
+
+ QVBoxLayout *topLayout = new QVBoxLayout(centralWidget,0);
+
+ /* button group #1 */
+ QButtonGroup* linLogGroup = new QHButtonGroup( centralWidget, "LinLogGroup" );
+ topLayout->addWidget(linLogGroup,0);
+
+ QRadioButton* rb1 = new QRadioButton( linLogGroup );
+ rb1->setText( i18n("L&og") );
+ QToolTip::add( rb1, i18n("Logarithmic Y scale") );
+
+ QRadioButton* rb2 = new QRadioButton( linLogGroup );
+ rb2->setText( i18n("L&inear") );
+ rb2->setChecked( TRUE );
+ QToolTip::add( rb2, i18n("Linear Y scale") );
+
+ fftwin = new FFTWindow(centralWidget,"fft");
+ topLayout->addWidget(fftwin,1);
+ QObject::connect(card,SIGNAL(senddata(void*)),
+ fftwin, SLOT(new_data(void*)));
+ QObject::connect(card,SIGNAL(newparams(struct SOUNDPARAMS*)),
+ fftwin, SLOT(new_params(struct SOUNDPARAMS*)));
+ QObject::connect(linLogGroup, SIGNAL(clicked(int)),
+ fftwin, SLOT(set_ylog(int)) );
+
+ setCaption("freq spectrum");
+
+#if 1
+ /* session management */
+ if (globalKapp->isRestored()) {
+ for (i = 1; canBeRestored(i); i++)
+ if (0 == strcmp(classNameOfToplevel(i),"KFFT"))
+ break;
+ if (!canBeRestored(i))
+ i = -1;
+ }
+ if (i > 0) {
+ restore(i);
+ } else {
+ resize(200,120);
+ }
+#else
+ resize(200,120);
+#endif
+}
+
+KFFT::~KFFT()
+{
+ delete fftwin;
+ fftwin = NULL;
+}
+
+void
+KFFT::showit()
+{
+ if (!isVisible())
+ show();
+}
+
+/* ------------------------------------------------------------------------ */
+
+KLevel::KLevel(Soundcard *card) : KMainWindow(0,"level",WType_TopLevel)
+{
+ QRadioButton *rb1;
+ QRadioButton *rb2;
+
+ thislevelwidget = new QWidget(this);
+ setCentralWidget(thislevelwidget);
+
+ QVBoxLayout *topLayout = new QVBoxLayout(thislevelwidget,0);
+ QHBoxLayout *hbox = new QHBoxLayout();
+ topLayout->addLayout(hbox);
+
+ /* button group #1 */
+ PowMaxGroup = new QButtonGroup( thislevelwidget, "PowMaxGroup" );
+ QHBoxLayout *vbox = new QHBoxLayout(PowMaxGroup);
+ hbox->addWidget(PowMaxGroup);
+
+ rb1 = new QRadioButton( PowMaxGroup );
+ rb1->setText( i18n("L&og") );
+ rb1->setChecked( TRUE );
+ vbox->addWidget(rb1);
+ rb1->setMinimumSize(rb1->sizeHint());
+ QToolTip::add( rb1, i18n("Logarithmic scale") );
+
+ rb2 = new QRadioButton( PowMaxGroup );
+ rb2->setText( i18n("L&inear") );
+ vbox->addWidget(rb2);
+ rb2->setMinimumSize( rb2->sizeHint() );
+ QToolTip::add( rb2, i18n("Linear scale") );
+
+ connect( PowMaxGroup, SIGNAL(clicked(int)), SLOT(LogvsLinearClicked(int)));
+
+ /* button group #2 */
+ LogLinGroup = new QButtonGroup( thislevelwidget, "LogLinGroup" );
+ vbox = new QHBoxLayout(LogLinGroup, 2);
+ hbox->addWidget( LogLinGroup, 0);
+
+ rb1 = new QRadioButton( LogLinGroup );
+ rb1->setText( i18n("&Power") );
+ rb1->setChecked( TRUE );
+ vbox->addWidget(rb1);
+ rb1->setMinimumSize( rb1->sizeHint() );
+ QToolTip::add( rb1, i18n("Display power carried by signal") );
+
+ rb2 = new QRadioButton( LogLinGroup );
+ rb2->setText( i18n("&Max") );
+ vbox->addWidget(rb2);
+ rb2->setMinimumSize( rb2->sizeHint() );
+ QToolTip::add( rb2, i18n("Display signal maximum level") );
+
+
+ /* level window */
+ levelwin = new LevelWindow(thislevelwidget,"level");
+ levelwin->setMinimumSize(40,10);
+ topLayout->addWidget(levelwin, 1);
+
+ QObject::connect(card,SIGNAL(senddata(void*)),
+ levelwin, SLOT(new_data(void*)));
+ QObject::connect(card,SIGNAL(newparams(struct SOUNDPARAMS*)),
+ levelwin, SLOT(new_params(struct SOUNDPARAMS*)));
+ setCaption("input level");
+
+
+ /* scale */
+ hbox = new QHBoxLayout();
+ topLayout->addLayout(hbox);
+ llabel = new QLabel(thislevelwidget,"llabel");
+ llabel->setText("-100 dB");
+ llabel->setAlignment(AlignLeft);
+ llabel->setMinimumSize(llabel->sizeHint());
+ mlabel = new QLabel(thislevelwidget,"mlabel");
+ mlabel->setText("-");
+ mlabel->setAlignment(AlignCenter);
+ mlabel->setMinimumSize(mlabel->sizeHint());
+ rlabel = new QLabel(thislevelwidget,"rlabel");
+ rlabel->setText("0 dB");
+ rlabel->setAlignment(AlignRight);
+ rlabel->setMinimumSize(llabel->sizeHint());
+ hbox->addWidget(llabel,1);
+ hbox->addWidget(mlabel,1);
+ hbox->addWidget(rlabel,1);
+
+ connect(LogLinGroup, SIGNAL(clicked(int)), SLOT(PowervsMaxClicked(int)));
+
+ connect(levelwin,SIGNAL(setvalue(char*)),
+ this,SLOT(setvalue(char*)));
+
+ /* show it */
+ setMaximumSize(800,80);
+ resize(320,100);
+ topLayout->activate();
+}
+
+KLevel::~KLevel()
+{
+ delete thislevelwidget;
+ thislevelwidget = NULL;
+ delete levelwin;
+ levelwin = NULL;
+}
+
+void
+KLevel::showit()
+{
+ if (!isVisible())
+ show();
+}
+
+void
+KLevel::resizeEvent( QResizeEvent * )
+{
+ thislevelwidget->resize(size());
+ thislevelwidget->show();
+}
+
+void
+KLevel::setvalue(char *text) {
+ mlabel->setText(text);
+}
+
+void
+KLevel::updatelabels() {
+ if (levelwin->PowervsMax) {
+ if (levelwin->LogvsLinear) {
+ llabel->setText("-100 dB");
+ rlabel->setText("0 dB");
+ } else {
+ llabel->setText("");
+ rlabel->setText("");
+ }
+ } else {
+ if (levelwin->LogvsLinear) {
+ llabel->setText("-50 dB");
+ rlabel->setText("0 dB");
+ } else {
+ llabel->setText("0%");
+ rlabel->setText("100%");
+ }
+ }
+ mlabel->setText("");
+}
+
+void
+KLevel::PowervsMaxClicked(int i) {
+ if (i==0)
+ levelwin->PowervsMax=true; else levelwin->PowervsMax=false;
+ updatelabels();
+};
+
+void
+KLevel::LogvsLinearClicked(int i) {
+ if (i==0)
+ levelwin->LogvsLinear=1; else levelwin->LogvsLinear=0;
+ updatelabels();
+};
diff --git a/krecord.h b/krecord.h
new file mode 100644
index 0000000..1066486
--- /dev/null
+++ b/krecord.h
@@ -0,0 +1,122 @@
+#ifndef KRECORD_H
+#define KRECORD_H
+
+#if 0
+#include <qfiledialog.h>
+#include <qmsgbox.h>
+#include <qpopmenu.h>
+#include <qmenubar.h>
+#include <qtooltip.h>
+#include <qlayout.h>
+#include <qpushbt.h>
+#include <qchkbox.h>
+#include <qbttngrp.h>
+#include <qradiobt.h>
+#include <qlistbox.h>
+#include <qaccel.h>
+
+#include <kapp.h>
+#include <kmessagebox.h>
+#include <kmenubar.h>
+#include <kmainwindow.h>
+#include <ktabctl.h>
+#endif
+
+class QDropEvent;
+
+/* ------------------------------------------------------------------------ */
+
+class KFFT : public KMainWindow
+{
+ Q_OBJECT;
+public:
+ KFFT(Soundcard *card);
+ ~KFFT();
+
+public slots:
+ void showit();
+
+private:
+ FFTWindow *fftwin;
+};
+
+class KLevel : public KMainWindow
+{
+ Q_OBJECT;
+public:
+ KLevel(Soundcard *card);
+ ~KLevel();
+
+protected:
+ void resizeEvent( QResizeEvent * );
+
+public slots:
+ void showit();
+ void updatelabels();
+ void setvalue(char *text);
+ void PowervsMaxClicked(int i);
+ void LogvsLinearClicked(int i);
+
+private:
+ LevelWindow *levelwin;
+ QWidget *thislevelwidget;
+ QButtonGroup *PowMaxGroup;
+ QButtonGroup *LogLinGroup;
+ QLabel *llabel;
+ QLabel *mlabel;
+ QLabel *rlabel;
+};
+
+/* ------------------------------------------------------------------------ */
+
+class KRecord : public KMainWindow
+{
+ Q_OBJECT
+public:
+ KRecord();
+ ~KRecord();
+ Soundcard *soundcard;
+ SoundOptions *soundopts;
+ QFileDialog *fdialog;
+ QAccel *accel;
+ KFFT *kfft;
+ KLevel *klevel;
+ BufferList *blist;
+
+protected:
+ void dropEvent(QDropEvent *);
+ void dragEnterEvent(QDragEnterEvent* event);
+
+public slots:
+ void new_file();
+ void save_as();
+ void quit_cb();
+
+ void record_options();
+ void exec_mixer();
+ void tb_toggle();
+ void sl_toggle();
+
+ void update_statusline(const char *text);
+ void update_statusline(struct SOUNDPARAMS *p);
+
+private:
+ void create_menu();
+ void create_toolbar();
+ void create_soundbar();
+ void create_statusline();
+
+ QPopupMenu *file_menu;
+ QPopupMenu *opt_menu;
+
+ KToolBar *toolbar;
+ KToolBar *soundbar;
+ int tb_mid;
+
+ QListBox *listwidget;
+
+ KStatusBar *statusline;
+ int sl_mid;
+};
+
+#endif
diff --git a/level.cpp b/level.cpp
new file mode 100644
index 0000000..e8560d8
--- /dev/null
+++ b/level.cpp
@@ -0,0 +1,438 @@
+/*
+ * level.cpp. Part of krecord by Gerd Knorr.
+ *
+ * Displays the input level.
+ *
+ * Copyright (C) 1998 Florian Kolbe
+ *
+ * History:
+ *
+ * Jun 04 1998 Florian Kolbe
+ * Created
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <math.h>
+#include <limits.h>
+
+#include <sys/types.h>
+
+#include <qwidget.h>
+#include <qpixmap.h>
+#include <qpainter.h>
+#include <qcolor.h>
+#include <qtimer.h>
+
+#include "sound.h"
+#include "level.moc"
+
+#ifndef min
+#define min(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef max
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+LevelWindow::LevelWindow(QWidget *parent, char *name):QWidget(parent,name,0)
+{
+ /* which type of vu-meter ? */
+ PowervsMax=1;
+ LogvsLinear=1;
+
+ /*
+ did not get sound-params yet.
+ */
+ init = FALSE;
+ sdata = NULL;
+
+ /*
+ no peaks initially.
+ */
+ peak[0] = 0;
+ peak[1] = 0;
+
+ clipLeft=false;
+ clipRight=false;
+
+ /*
+ will use output buffer, so no background necessary.
+ */
+ setBackgroundMode(NoBackground);
+ orange = QColor("orangered");
+
+ /*
+ Initialize output buffer.
+ */
+ buffer = new QPixmap(size());
+
+ /*
+ Initialize timers to reset peaks.
+ */
+ timer[0] = new QTimer();
+ timer[1] = new QTimer();
+ connect(timer[0], SIGNAL(timeout()), this, SLOT(resetPeakLeft()));
+ connect(timer[1], SIGNAL(timeout()), this, SLOT(resetPeakRight()));
+
+} /* LevelWindow */
+
+LevelWindow::~LevelWindow(void)
+{
+ delete buffer;
+ delete timer[0];
+ delete timer[1];
+}
+
+void LevelWindow::resizeEvent(QResizeEvent*)
+{
+ /*
+ Fix size of output buffer.
+ */
+ delete buffer;
+ buffer = new QPixmap(size());
+}
+
+void LevelWindow::resetPeakLeft(void)
+{
+ peak[0] = 0;
+ clipLeft = false;
+ repaint();
+}
+
+void LevelWindow::resetPeakRight(void)
+{
+ peak[1] = 0;
+ clipRight = false;
+ repaint();
+}
+
+void LevelWindow::drawBar(QPainter& painter, int channel, float level,
+ int size, bool drawRed)
+{
+ int xLevel = (int)(((float)width())*level); /* x-pos of current level */
+ int x80 = width()*80/100; /* x-pos of 80% level */
+ int y,x;
+
+ /*
+ Left/mono top, right bottom.
+ */
+ if (channel == 0) {
+ y = 0;
+ } else {
+ y = height()/2;
+ }
+
+ const QColor *colortodraw=&darkGreen;
+ if (drawRed) {
+ colortodraw=&red;
+ }
+
+ /*
+ Green: 0%-[level|80%]
+ */
+ painter.fillRect(0, y+1, max(1, min(xLevel, x80)), size-2, *colortodraw);
+
+ /*
+ Yellow part.
+ */
+ if (!drawRed) {
+ colortodraw=&darkYellow;
+ }
+
+ if (level > 0.8) {
+ painter.fillRect(x80, y+1, max(1, xLevel-x80), size-2, *colortodraw);
+ }
+
+ /*
+ Current peak is either reached again or pushed.
+ */
+ if (level >= peak[channel]) {
+ peak[channel] = level;
+ timer[channel]->start(1000, TRUE);
+ }
+
+ /*
+ Draw peak if greater than current level.
+ */
+ if (peak[channel] >= level) {
+
+ /*
+ 0- 80: green
+ 80- 98: yellow
+ 99-100: orange
+ */
+ painter.setPen(green);
+ if (peak[channel] > 0.80) {
+ painter.setPen(yellow);
+ }
+ if (peak[channel] >= 0.99) {
+ painter.setPen(orange);
+ }
+ x = (int)min(width()*peak[channel]-1,width()-1);
+ painter.drawLine(x, y+1, x, y+size-2);
+ }
+} /* drawBar */
+
+void LevelWindow::paintEvent(QPaintEvent*)
+{
+ int maxLeft = 0;
+ int maxRight = 0;
+ int i;
+ QPainter painter;
+ int maxAmp;
+ int64_t powerLeft=0;
+ int64_t powerRight=0;
+ float floatPowerLeft=0;
+ float floatPowerRight=0;
+ char buf[32];
+
+#ifndef NO_COMPENSATE_BIAS
+ int64_t bLeft=0;
+ int64_t bRight=0;
+#endif
+
+
+ if ((init == FALSE) || !sdata) return;
+
+ /* if true then calculate Power else calculate Max */
+ if (PowervsMax) {
+ /*
+ Calculate power of the signal depending on format.
+
+ Since the signal may not have an average value of 0 precisely,
+ we shouldn't simply calculate:
+
+ sum_for_all_samples (pulse_value²) / number_of_samples
+
+ but this formula assumes that the average is zero, which is not
+ always true (for example, in 8 bits on a Sound Blaster 64,
+ there is always a shift by one unit.
+
+ We could calculate in two passes, first the average, then the
+ power of the measure minus the average. But we can do this in
+ one pass.
+
+ Let measure = signal + bias,
+ where measure is the pulse value,
+ signal is what we want,
+ bias is a constant, such that the average of signal is zero.
+
+ What we want is the value of: power = sum_for_all_samples (signal²)
+
+ Let's calculate in the same pass:
+ a=sum_for_all_samples (measure²)
+ and
+ b=sum_for_all_samples (measure)
+
+ Then a and b are equivalent to:
+ a = sum_for_all_samples (measure²)
+ = sum_for_all_samples ((signal + bias)²)
+ = sum_for_all_samples (signal² + bias²)
+ = sum_for_all_samples (signal²) + number_of_samples * bias²
+
+ and
+ b = sum_for_all_samples (measure)
+ = bias * number_of_samples
+ that is, number_of_samples * bias² = b² / number_of_samples
+
+ So a = power + b² / number_of_samples
+
+ And power = a - b² / number_of_samples
+
+ So we've got the correct power of the signal in one pass.
+
+ */
+
+#ifndef NO_COMPENSATE_BIAS
+ bLeft=0;
+ bRight=0;
+#endif
+
+ if (afmt == FMT_16BIT) {
+
+ maxAmp = 32768;
+ if (channels == 1) {
+ for (i = 0; i < samples; i++) {
+ /* Since we calculate the square of something that can be
+ as big as +-32767 we assume a width of at least 32 bits
+ for a signed int. Moreover, we add a thousand of these
+ to calculate power, so 32 bits aren't enough. I chose 64
+ bits unsigned int for precision. We could have switched
+ to float or double instead... */
+ signed int thispulse=(sdata[i]);
+ /* Note: we calculate max value anyway, to detect clipping */
+ if (abs(thispulse) > maxLeft) maxLeft = abs(sdata[i]);
+ powerLeft+=(thispulse*thispulse);
+#ifndef NO_COMPENSATE_BIAS
+ bLeft+=thispulse;
+#endif
+ }
+ } else
+ if (channels == 2) {
+ for (i = 0; i < samples; i++) {
+ signed int thispulse=(sdata[i*2]);
+ if (abs(thispulse) > maxLeft) maxLeft = abs(thispulse);
+ powerLeft+=(thispulse*thispulse);
+ thispulse=(sdata[i*2+1]);
+ if (abs(thispulse) > maxRight) maxRight = abs(thispulse);
+ powerRight+=(thispulse*thispulse);
+#ifndef NO_COMPENSATE_BIAS
+ bRight+=thispulse;
+#endif
+ }
+ }
+ } else {
+ unsigned char* bdata = (unsigned char*)sdata;
+
+ maxAmp = 128;
+
+ if (channels == 1) {
+ for (i = 0; i < samples; i++) {
+ signed int thispulse=(bdata[i]-128);
+ if (abs(thispulse) > maxLeft) maxLeft = abs(thispulse);
+ powerLeft+=(thispulse*thispulse);
+#ifndef NO_COMPENSATE_BIAS
+ bLeft+=thispulse;
+#endif
+ }
+ } else
+ if (channels == 2) {
+ for (i = 0; i < samples; i++) {
+ signed int thispulse=(bdata[i*2]-128);
+ if (abs(thispulse) > maxLeft) maxLeft = abs(thispulse);
+ powerLeft+=(thispulse*thispulse);
+ thispulse=(bdata[i*2+1]-128);
+ if (abs(thispulse) > maxRight) maxRight = abs(thispulse);
+ powerRight+=(thispulse*thispulse);
+#ifndef NO_COMPENSATE_BIAS
+ bRight+=thispulse;
+#endif
+ }
+ }
+ }
+ /* Ok for raw power. Now normalize it. */
+
+#ifndef NO_COMPENSATE_BIAS
+ powerLeft-=bLeft*bLeft/samples;
+ powerRight-=bRight*bRight/samples;
+ //fprintf(stderr, "bLeft: %lld\tbiais: %f\t", bLeft, ((float)bLeft*(float)maxAmp/(float)samples));
+#endif
+
+ floatPowerLeft=((float)powerLeft)/((float)maxAmp)/((float)maxAmp)/((float)samples);
+ floatPowerRight=((float)powerLeft)/((float)maxAmp)/((float)maxAmp)/((float)samples);
+
+ //fprintf(stderr, "brute: %lld,\tnormalisée: %f\t", powerLeft, floatPowerLeft);
+ } else {
+ /*
+ Find max amplitude depending on format.
+ */
+ if (afmt == FMT_16BIT) {
+
+ maxAmp = 32768;
+ if (channels == 1) {
+ for (i = 0; i < samples; i++)
+ if (abs(sdata[i]) > maxLeft) maxLeft = abs(sdata[i]);
+ } else
+ if (channels == 2) {
+ for (i = 0; i < samples; i++) {
+ if (abs(sdata[i*2]) > maxLeft) maxLeft = abs(sdata[i*2]);
+ if (abs(sdata[i*2+1]) > maxRight) maxRight = abs(sdata[i*2+1]);
+ }
+ }
+ } else {
+ unsigned char* bdata = (unsigned char*)sdata;
+
+ maxAmp = 128;
+
+ if (channels == 1) {
+ for (i = 0; i < samples; i++)
+ if (abs(bdata[i]-128) > maxLeft) maxLeft = abs(bdata[i]-128);
+ } else
+ if (channels == 2) {
+ for (i = 0; i < samples; i++) {
+ if (abs(bdata[i*2]-128) > maxLeft) maxLeft = abs(bdata[i*2]-128);
+ if (abs(bdata[i*2+1]-128) > maxRight) maxRight = abs(bdata[i*2+1]-128);
+ }
+ }
+ }
+ }
+
+ if (maxLeft>=(maxAmp-1)) clipLeft=true;
+ if (maxRight>=(maxAmp-1)) clipRight=true;
+
+ /*
+ Draw bars.
+ */
+ buffer->fill(black /* backgroundColor() */);
+ painter.begin(buffer);
+
+ if (PowervsMax) { /* Power */
+ if (LogvsLinear) { /* Log */
+ /* we want leftmost to be 100dB
+ (though signal-to-noise ratio can't be more than 96.33dB in power)
+ and rightmost to be 0dB (maximum power) */
+ float dBvalue=1+0.1*log10(floatPowerLeft); /* 10/100 = 0.1 */
+ //fprintf(stderr, "dB: %f\r", -10*log10(floatPowerLeft));
+ sprintf(buf, "%.2f dB", -10*log10(floatPowerLeft));
+ emit setvalue(buf);
+
+ drawBar(painter, 0, dBvalue, height()/channels, clipLeft);
+ if (channels == 2) {
+ dBvalue=1+0.1*log10(floatPowerRight);
+ drawBar(painter, 1, dBvalue, height()/channels, clipRight);
+ }
+ } else { /* Linear */
+ drawBar(painter, 0, floatPowerLeft, height()/channels, clipLeft);
+ if (channels == 2) {
+ drawBar(painter, 1, floatPowerRight, height()/channels, clipRight);
+ }
+ }
+ } else { /* Max */
+ if (LogvsLinear) { /* Log */
+ /* we want leftmost to be 50dB
+ (though signal-to-noise ratio can't be more than 48.16dB in amplitude)
+ and rightmost to be 0dB (clipping trheshold reached!) */
+ float logvalue=1+0.2*log10((float)maxLeft/(float)maxAmp); /* 10/50 = 0.2 */
+ drawBar(painter, 0, logvalue, height()/channels, clipLeft);
+ if (channels == 2) {
+ logvalue=1+0.2*log10((float)maxRight/(float)maxAmp);
+ drawBar(painter, 1, logvalue, height()/channels, clipRight);
+ }
+ } else { /* Linear */
+ float value=((float)maxLeft/(float)maxAmp);
+ drawBar(painter, 0, value, height()/channels, clipLeft);
+ if (channels == 2) {
+ value=((float)maxRight/(float)maxAmp);
+ drawBar(painter, 1, value, height()/channels, clipRight);
+ }
+ }
+ };
+
+ painter.end();
+ bitBlt(this, 0, 0, buffer);
+
+} /* paintEvent */
+
+void LevelWindow::new_params(struct SOUNDPARAMS *p)
+{
+ afmt = p->format;
+ channels = p->channels;
+ samples = p->blocksize/channels/(afmt == FMT_16BIT ? 2 : 1);
+
+ init = TRUE;
+
+} /* new_params */
+
+void LevelWindow::new_data(void *data)
+{
+
+ sdata = (short int*)data;
+ repaint();
+
+} /* new_data */
diff --git a/level.h b/level.h
new file mode 100644
index 0000000..9d15d19
--- /dev/null
+++ b/level.h
@@ -0,0 +1,56 @@
+/*
+ * level.h. Part of krecord by Gerd Knorr.
+ *
+ * Displays the input level.
+ *
+ * Copyright (C) 1998 Florian Kolbe
+ *
+ * History see level.cpp
+ *
+ */
+
+#ifndef LEVEL_H
+#define LEVEL_H
+
+#include <qwidget.h>
+
+class LevelWindow : public QWidget
+{
+ Q_OBJECT;
+
+public:
+ LevelWindow(QWidget *parent, char *name);
+ ~LevelWindow(void);
+ bool PowervsMax;
+ bool LogvsLinear;
+
+public slots:
+ void new_params(struct SOUNDPARAMS *params);
+ void new_data(void *data);
+ void resetPeakLeft();
+ void resetPeakRight();
+
+protected:
+ void paintEvent (QPaintEvent *);
+ void resizeEvent(QResizeEvent *);
+
+private:
+ int init;
+ int afmt;
+ int samples;
+ int channels;
+ signed short* sdata;
+ QColor orange;
+ QPixmap* buffer;
+ QTimer* timer[2];
+ float peak[2];
+ bool clipLeft;
+ bool clipRight;
+
+ void drawBar(QPainter& painter, int where, float level, int size, bool drawRed);
+
+signals:
+ void setvalue(char *text);
+};
+
+#endif /* LEVEL_H */
diff --git a/mix.c b/mix.c
new file mode 100644
index 0000000..cb9b1df
--- /dev/null
+++ b/mix.c
@@ -0,0 +1,75 @@
+/*
+ * dump current mixer settings
+ *
+ * (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+
+char *labels[] = SOUND_DEVICE_LABELS;
+char *names[] = SOUND_DEVICE_NAMES;
+
+int
+dump_mixer(char *devname)
+{
+ struct mixer_info info;
+ int mix,i,devmask,recmask,recsrc,stereomask,volume;
+
+ if (-1 == (mix = open(devname,O_RDONLY)))
+ return -1;
+
+ printf("%s",devname);
+ if (-1 != ioctl(mix,SOUND_MIXER_INFO,&info))
+ printf(" = %s (%s)",info.id,info.name);
+ printf("\n");
+
+ if (-1 == ioctl(mix,MIXER_READ(SOUND_MIXER_DEVMASK),&devmask) ||
+ -1 == ioctl(mix,MIXER_READ(SOUND_MIXER_STEREODEVS),&stereomask) ||
+ -1 == ioctl(mix,MIXER_READ(SOUND_MIXER_RECMASK),&recmask) ||
+ -1 == ioctl(mix,MIXER_READ(SOUND_MIXER_RECSRC),&recsrc)) {
+ perror("mixer ioctl");
+ return -1;
+ }
+
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
+ if ((1<<i) & devmask) {
+ if (-1 == ioctl(mix,MIXER_READ(i),&volume)) {
+ perror("mixer read volume");
+ return -1;
+ }
+ printf(" %-10s (%2d) : %s %s%s",
+ names[i],i,
+ (1<<i) & stereomask ? "stereo" : "mono ",
+ (1<<i) & recmask ? "rec" : " ",
+ (1<<i) & recsrc ? "*" : " ");
+ if ((1<<i) & stereomask)
+ printf(" %d/%d\n",volume & 0xff,(volume >> 8) & 0xff);
+ else
+ printf(" %d\n",volume & 0xff);
+ }
+ }
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char devname[32];
+ int i;
+
+ /* first mixer device. If "mixer0" does'nt work, try "mixer" */
+ if (-1 == dump_mixer("/dev/mixer0"))
+ dump_mixer("/dev/mixer");
+ /* other more devices */
+ for (i = 1; i < 8; i++) {
+ sprintf(devname,"/dev/mixer%d",i);
+ dump_mixer(devname);
+ }
+ return 0;
+}
diff --git a/mk/Autoconf.mk b/mk/Autoconf.mk
new file mode 100644
index 0000000..4d25d21
--- /dev/null
+++ b/mk/Autoconf.mk
@@ -0,0 +1,138 @@
+#
+# simple autoconf system for GNU make
+#
+# (c) 2002-2004 Gerd Knorr <kraxel@bytesex.org>
+#
+# credits for creating this one go to the autotools people because
+# they managed it to annoy lots of developers and users (including
+# me) with version incompatibilities.
+#
+# This file is public domain. No warranty. If it breaks you keep
+# both pieces.
+#
+########################################################################
+
+# verbose yes/no
+verbose ?= no
+
+# some stuff used by the tests
+ifneq ($(verbose),no)
+ # verbose (for debug)
+ ac_init = echo "checking $(1) ... " >&2; rc=no
+ ac_b_cmd = echo "run: $(1)" >&2; $(1) >/dev/null && rc=yes
+ ac_s_cmd = echo "run: $(1)" >&2; rc=`$(1)`
+ ac_fini = echo "... result is $${rc}" >&2; echo >&2; echo "$${rc}"
+else
+ # normal
+ ac_init = echo -n "checking $(1) ... " >&2; rc=no
+ ac_b_cmd = $(1) >/dev/null 2>&1 && rc=yes
+ ac_s_cmd = rc=`$(1) 2>/dev/null`
+ ac_fini = echo "$${rc}" >&2; echo "$${rc}"
+endif
+
+# some helpers to build cflags and related variables
+ac_def_cflags_1 = $(if $(filter yes,$($(1))),-D$(1))
+ac_lib_cflags = $(foreach lib,$(1),$(call ac_def_cflags_1,HAVE_LIB$(lib)))
+ac_inc_cflags = $(foreach inc,$(1),$(call ac_def_cflags_1,HAVE_$(inc)))
+ac_lib_mkvar_1 = $(if $(filter yes,$(HAVE_LIB$(1))),$($(1)_$(2)))
+ac_lib_mkvar = $(foreach lib,$(1),$(call ac_lib_mkvar_1,$(lib),$(2)))
+
+
+########################################################################
+# the tests ...
+
+# get uname
+ac_uname = $(shell \
+ $(call ac_init,for system);\
+ $(call ac_s_cmd,uname -s | tr 'A-Z' 'a-z');\
+ $(call ac_fini))
+
+# check for some header file
+# args: header file
+ac_header = $(shell \
+ $(call ac_init,for $(1));\
+ $(call ac_b_cmd,echo '\#include <$(1)>' |\
+ $(CC) $(CFLAGS) -E -);\
+ $(call ac_fini))
+
+# check for some function
+# args: function [, additional libs ]
+ac_func = $(shell \
+ $(call ac_init,for $(1));\
+ echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
+ > __actest.c;\
+ $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
+ __actest __actest.c $(2));\
+ rm -f __actest __actest.c;\
+ $(call ac_fini))
+
+# check for some library
+# args: function, library [, additional libs ]
+ac_lib = $(shell \
+ $(call ac_init,for $(1) in $(2));\
+ echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
+ > __actest.c;\
+ $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
+ __actest __actest.c -l$(2) $(3));\
+ rm -f __actest __actest.c;\
+ $(call ac_fini))
+
+# check if some compiler flag works
+# args: compiler flag
+ac_cflag = $(shell \
+ $(call ac_init,if $(CC) supports $(1));\
+ echo 'int main() {return 0;}' > __actest.c;\
+ $(call ac_b_cmd,$(CC) $(CFLAGS) $(1) $(LDFLAGS) -o \
+ __actest __actest.c);\
+ rm -f __actest __actest.c;\
+ $(call ac_fini))
+
+# check for some binary
+# args: binary name
+ac_binary = $(shell \
+ $(call ac_init,for $(1));\
+ $(call ac_s_cmd,which $(1));\
+ bin="$$rc";rc="no";\
+ $(call ac_b_cmd,test -x "$$$$bin");\
+ $(call ac_fini))
+
+# check if lib64 is used
+ac_lib64 = $(shell \
+ $(call ac_init,for libdir name);\
+ $(call ac_s_cmd,$(CC) -print-search-dirs | grep -q lib64 &&\
+ echo "lib64" || echo "lib");\
+ $(call ac_fini))
+
+# check for x11 ressource dir prefix
+ac_resdir = $(shell \
+ $(call ac_init,for X11 app-defaults prefix);\
+ $(call ac_s_cmd, test -d /etc/X11/app-defaults &&\
+ echo "/etc/X11" || echo "/usr/X11R6/lib/X11");\
+ $(call ac_fini))
+
+
+########################################################################
+# build Make.config
+
+define newline
+
+
+endef
+make-config-q = $(subst $(newline),\n,$(make-config))
+
+ifeq ($(filter config,$(MAKECMDGOALS)),config)
+.PHONY: Make.config
+ LIB := $(call ac_lib64)
+else
+ LIB ?= $(call ac_lib64)
+ LIB := $(LIB)
+endif
+.PHONY: config
+config: Make.config
+ @true
+
+Make.config: $(srcdir)/GNUmakefile
+ @echo -e "$(make-config-q)" > $@
+ @echo
+ @echo "Make.config written, edit if needed"
+ @echo
diff --git a/mk/Compile.mk b/mk/Compile.mk
new file mode 100644
index 0000000..49dddbf
--- /dev/null
+++ b/mk/Compile.mk
@@ -0,0 +1,88 @@
+#
+# some rules to compile stuff ...
+#
+# (c) 2002-2004 Gerd Knorr <kraxel@bytesex.org>
+#
+# main features:
+# * autodependencies via "cpp -MD"
+# * fancy, non-verbose output
+#
+# This file is public domain. No warranty. If it breaks you keep
+# both pieces.
+#
+########################################################################
+
+# verbose yes/no
+verbose ?= no
+
+# dependency files
+tmpdep = mk/$(subst /,_,$*).tmp
+depfile = mk/$(subst /,_,$*).dep
+depfiles = mk/*.dep
+
+compile_c = $(CC) $(CFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
+compile_cc = $(CXX) $(CXXFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
+fixup_deps = sed -e "s|.*\.o:|$@:|" < $(tmpdep) > $(depfile) && rm -f $(tmpdep)
+cc_makedirs = mkdir -p $(dir $@) $(dir $(depfile))
+
+link_app = $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+link_so = $(CC) $(LDFLAGS) -shared -Wl,-soname,$(@F) -o $@ $^ $(LDLIBS)
+ar_lib = rm -f $@ && ar -r $@ $^ && ranlib $@
+
+moc_h = $(MOC) $< -o $@
+msgfmt_po = msgfmt -o $@ $<
+
+# non-verbose output
+ifeq ($(verbose),no)
+ echo_compile_c = echo " CC " $@
+ echo_compile_cc = echo " CXX " $@
+ echo_link_app = echo " LD " $@
+ echo_link_so = echo " LD " $@
+ echo_ar_lib = echo " AR " $@
+ echo_moc_h = echo " MOC " $@
+ echo_msgfmt_po = echo " MSGFMT " $@
+else
+ echo_compile_c = echo $(compile_c)
+ echo_compile_cc = echo $(compile_cc)
+ echo_link_app = echo $(link_app)
+ echo_link_so = echo $(link_so)
+ echo_ar_lib = echo $(ar_lib)
+ echo_moc_h = echo $(moc_h)
+ echo_msgfmt_po = echo $(msgfmt_po)
+endif
+
+%.o: %.c
+ @$(cc_makedirs)
+ @$(echo_compile_c)
+ @$(compile_c)
+ @$(fixup_deps)
+
+%.o: %.cc
+ @$(cc_makedirs)
+ @$(echo_compile_cc)
+ @$(compile_cc)
+ @$(fixup_deps)
+
+%.o: %.cpp
+ @$(cc_makedirs)
+ @$(echo_compile_cc)
+ @$(compile_cc)
+ @$(fixup_deps)
+
+
+%.so: %.o
+ @$(echo_link_so)
+ @$(link_so)
+
+%: %.o
+ @$(echo_link_app)
+ @$(link_app)
+
+%.moc : %.h
+ @$(echo_moc_h)
+ @$(moc_h)
+
+%.mo : %.po
+ @$(echo_msgfmt_po)
+ @$(msgfmt_po)
+
diff --git a/mk/Maintainer.mk b/mk/Maintainer.mk
new file mode 100644
index 0000000..5bf9480
--- /dev/null
+++ b/mk/Maintainer.mk
@@ -0,0 +1,12 @@
+# just some maintainer stuff for me ...
+########################################################################
+
+make-sync-dir = $(HOME)/src/gnu-make
+
+.PHONY: sync
+sync:: distclean
+ test -d $(make-sync-dir)
+ rm -f $(srcdir)/INSTALL $(srcdir)/mk/*.mk
+ cp -v $(make-sync-dir)/INSTALL $(srcdir)/.
+ cp -v $(make-sync-dir)/*.mk $(srcdir)/mk
+ chmod 444 $(srcdir)/INSTALL $(srcdir)/mk/*.mk
diff --git a/mk/Variables.mk b/mk/Variables.mk
new file mode 100644
index 0000000..930f824
--- /dev/null
+++ b/mk/Variables.mk
@@ -0,0 +1,46 @@
+# common variables ...
+########################################################################
+
+# directories
+DESTDIR =
+srcdir ?= .
+prefix ?= /usr/local
+bindir = $(DESTDIR)$(prefix)/bin
+mandir = $(DESTDIR)$(prefix)/share/man
+locdir = $(DESTDIR)$(prefix)/share/locale
+
+# package + version
+empty :=
+space := $(empty) $(empty)
+ifneq ($(wildcard $(srcdir)/VERSION),)
+ VERSION := $(shell cat $(srcdir)/VERSION)
+else
+ VERSION := 42
+endif
+
+# programs
+CC ?= gcc
+CXX ?= g++
+MOC ?= $(if $(QTDIR),$(QTDIR)/bin/moc,moc)
+INSTALL ?= install
+INSTALL_BINARY := $(INSTALL) -s
+INSTALL_SCRIPT := $(INSTALL)
+INSTALL_DATA := $(INSTALL) -m 644
+INSTALL_DIR := $(INSTALL) -d
+
+# cflags
+CFLAGS ?= -g -O2
+CFLAGS += -Wall -Wmissing-prototypes -Wstrict-prototypes \
+ -Wpointer-arith -Wunused
+
+# add /usr/local to the search path if something is in there ...
+ifneq ($(wildcard /usr/local/include/*.h),)
+ CFLAGS += -I/usr/local/include
+ LDFLAGS += -L/usr/local/$(LIB)
+endif
+
+# fixup include path for $(srcdir) != "."
+ifneq ($(srcdir),.)
+ CFLAGS += -I. -I$(srcdir)
+endif
+
diff --git a/oss.cpp b/oss.cpp
new file mode 100644
index 0000000..af41cf2
--- /dev/null
+++ b/oss.cpp
@@ -0,0 +1,358 @@
+#if defined(HAVE_SOUNDCARD_H) || defined(HAVE_SYS_SOUNDCARD_H)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_SOUNDCARD_H
+# include <soundcard.h>
+#endif
+#ifdef HAVE_SYS_SOUNDCARD_H
+# include <sys/soundcard.h>
+#endif
+
+#include "sound.h"
+#include "oss.moc"
+
+#ifndef OSS_GETVERSION
+#define OSS_GETVERSION _IOR('M',118,int)
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+Soundcard::Soundcard(const char *dev)
+{
+ if (dev)
+ strcpy(devname,dev);
+ else
+ strcpy(devname,"/dev/dsp");
+
+ driver_name[0] = '\0';
+
+ stat = STATUS_CLOSED;
+ get_capabilities();
+ channels = 1;
+ rate = 22050;
+ fd = -1;
+}
+
+Soundcard::~Soundcard()
+{
+ /* nothing */
+}
+
+int
+Soundcard::start_record()
+{
+ switch (stat) {
+ case STATUS_CLOSED:
+ if (!init_done)
+ get_capabilities();
+ if (!init_done)
+ return -1;
+ return open_dev(TRUE);
+ case STATUS_RECORD:
+ return 0;
+ case STATUS_PLAYBACK:
+ close_dev();
+ return open_dev(TRUE);
+ }
+ return -1;
+}
+
+int
+Soundcard::start_playback()
+{
+ switch (stat) {
+ case STATUS_CLOSED:
+ if (!init_done)
+ get_capabilities();
+ if (!init_done)
+ return -1;
+ return open_dev(FALSE);
+ case STATUS_RECORD:
+ close_dev();
+ return open_dev(FALSE);
+ case STATUS_PLAYBACK:
+ return 0;
+ }
+ return -1;
+}
+
+int
+Soundcard::kill_buffer()
+{
+ ioctl(fd,SNDCTL_DSP_RESET,0);
+ return 0;
+}
+
+int
+Soundcard::stop()
+{
+ if (stat != STATUS_CLOSED)
+ close_dev();
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+Soundcard::get_capabilities()
+{
+ int i,dsp;
+ int try_afmt;
+ int try_channels;
+
+ afmt = 0;
+ if (-1 != (dsp = open(devname, O_RDONLY))) {
+
+ ioctl(dsp, SNDCTL_DSP_SETFMT, &afmt); /* current */
+ ioctl(dsp, SNDCTL_DSP_GETFMTS, &afmt_hw); /* hardware cap */
+ afmt_sw = 0;
+
+ for (i = 0; i < 16 /* XXX */; i++) {
+ try_afmt = (1<<i);
+ if (-1 == ioctl(dsp, SNDCTL_DSP_SETFMT, &try_afmt))
+ continue;
+ if (try_afmt != (1<<i))
+ continue;
+ afmt_sw |= try_afmt;
+ }
+
+ try_channels = 2;
+ if (-1 != ioctl(dsp, SNDCTL_DSP_CHANNELS, &try_channels) &&
+ 2 == try_channels)
+ channels_hw = 2;
+ else
+ channels_hw = 1;
+
+ /* version check */
+ if (-1 == ioctl(dsp,OSS_GETVERSION,&i)) {
+ strcpy(driver_name,"OSS (version unknown)");
+ } else {
+ sprintf(driver_name,"OSS %d.%d.%d%c",
+ (i>>16) & 0xff,(i>>8) & 0xff,(i>>4) & 0xf,(i&0xf)+'a');
+ }
+
+ close(dsp);
+ init_done = 1;
+
+ } else {
+ init_done = 0;
+ }
+}
+
+int
+Soundcard::has_channels()
+{
+ if (!init_done)
+ return -1;
+ return channels_hw;
+}
+
+int
+Soundcard::has_format(int f)
+{
+ if (!init_done)
+ return -1;
+ switch (f) {
+ case FMT_8BIT:
+ return (afmt_hw & AFMT_U8) ? 1 : 0;
+ break;
+ case FMT_16BIT:
+ return (afmt_hw & AFMT_S16_LE) ? 1 : 0;
+ break;
+ case FMT_MULAW:
+ case FMT_ALAW:
+ default:
+ return 0;
+ }
+}
+
+char*
+Soundcard::driver()
+{
+ return driver_name;
+}
+
+int
+Soundcard::open_dev(int record)
+{
+ struct SOUNDPARAMS p;
+ int frag,rrate;
+
+ if (-1 == (fd = open(devname,record ? O_RDONLY : O_WRONLY)))
+ goto err;
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+
+ /* try to get ~50 ms latency */
+ blocksize = 50*channels*rate/1000;
+ if (afmt == AFMT_U16_BE || afmt == AFMT_S16_BE ||
+ afmt == AFMT_U16_LE || afmt == AFMT_S16_LE)
+ blocksize *= 2;
+ for (frag = 0; blocksize != 1; frag++)
+ blocksize >>= 1;
+#if 0
+ fprintf(stderr,"asking for %d byte blocksize\n",1 << frag);
+#endif
+ frag |= 0x7fff0000;
+ if (-1 == ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag))
+ perror("ioctl SNDCTL_DSP_SETFRAGMENT");
+
+ rrate = rate;
+ if (-1 == ioctl(fd, SNDCTL_DSP_SETFMT, &afmt)) {
+ perror("ioctl SNDCTL_DSP_SETFMT");
+ goto err;
+ }
+ if (-1 == ioctl(fd, SNDCTL_DSP_CHANNELS, &channels)) {
+ perror("ioctl SNDCTL_DSP_SETFMT");
+ goto err;
+ }
+ if (-1 == ioctl(fd, SNDCTL_DSP_SPEED, &rrate)) {
+ perror("ioctl SNDCTL_DSP_SETFMT");
+ goto err;
+ }
+ if (-1 == ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize)) {
+ perror("ioctl SNDCTL_DSP_SETFMT");
+ goto err;
+ }
+ if (0 == blocksize)
+ blocksize = 4096;
+ if (rrate != rate) {
+ fprintf(stderr,"sample rate: asked for %d, hardware uses %d. ",
+ rate,rrate);
+ if (abs(rate-rrate)*100 < rate) {
+ fprintf(stderr,"that's fine (diff <1%%).\n");
+ } else {
+ fprintf(stderr,"way off, using hardware rate.\n");
+ rate = rrate;
+ }
+ }
+
+ latency = blocksize*1000/channels/rate;
+ if (afmt == AFMT_U16_BE || afmt == AFMT_S16_BE ||
+ afmt == AFMT_U16_LE || afmt == AFMT_S16_LE)
+ latency = latency/2;
+
+ telmi = new QSocketNotifier
+ (fd, record ? QSocketNotifier::Read : QSocketNotifier::Write);
+ QObject::connect(telmi,SIGNAL(activated(int)),
+ this, SLOT(sounddata(int)));
+
+ stat = record ? STATUS_RECORD : STATUS_PLAYBACK;
+#if 0
+ fprintf(stderr,"%s (format=%d, %s, rate=%d, blocksize=%d, latency=%d ms)\n",
+ record ? "recording" : "playback",
+ afmt,
+ (channels == 2) ? "stereo" : "mono",
+ rate, blocksize, latency);
+#endif
+ p.channels = channels;
+ p.rate = rate;
+ p.blocksize = blocksize;
+ p.latency = latency;
+ switch (afmt) {
+ case AFMT_U8: p.format = FMT_8BIT; break;
+ case AFMT_S16_LE: p.format = FMT_16BIT; break;
+ default: fprintf(stderr,"oops(open): unsupported sound format\n"); exit(1);
+ }
+ emit newparams(&p);
+
+ if (record) {
+ trigger = ~PCM_ENABLE_INPUT;
+ ioctl(fd,SNDCTL_DSP_SETTRIGGER,&trigger);
+ trigger = PCM_ENABLE_INPUT;
+ ioctl(fd,SNDCTL_DSP_SETTRIGGER,&trigger);
+ }
+ return 0;
+
+err:
+ if (-1 != fd)
+ close(fd);
+ stat = STATUS_CLOSED;
+ fd = -1;
+ return -1;
+}
+
+void
+Soundcard::close_dev()
+{
+ close(fd);
+ fd = -1;
+ stat = STATUS_CLOSED;
+
+ delete telmi;
+ return;
+}
+
+void
+Soundcard::setparams(struct SOUNDPARAMS *p)
+{
+ rate = p->rate;
+ channels = p->channels;
+ switch (p->format) {
+ case FMT_8BIT: afmt = AFMT_U8; break;
+ case FMT_16BIT: afmt = AFMT_S16_LE; break;
+ default: fprintf(stderr,"oops(set): unsupported sound format\n"); exit(1);
+ }
+
+ switch (stat) {
+ case STATUS_RECORD:
+ close_dev();
+ open_dev(TRUE);
+ break;
+ case STATUS_PLAYBACK:
+ close_dev();
+ open_dev(FALSE);
+ break;
+ case STATUS_CLOSED:
+ if (!init_done)
+ get_capabilities();
+ if (!init_done)
+ return;
+ if (0 == open_dev(TRUE))
+ close_dev();
+ break;
+ }
+}
+
+void
+Soundcard::sounddata(int s)
+{
+ int rc,have;
+
+ switch (stat) {
+ case STATUS_RECORD:
+ /* read */
+ for (have = 0; have < blocksize;) {
+ rc = read(fd,buffer+have,blocksize-have);
+ switch (rc) {
+ case -1:
+ if (EINTR != errno) {
+ perror("read sound");
+ exit(1);
+ }
+ break;
+ case 0:
+ fprintf(stderr,"Huh? got 0 bytes from sound device?\n");
+ exit(1);
+ default:
+ have += rc;
+ }
+ }
+ emit senddata((void*)buffer);
+ break;
+ case STATUS_PLAYBACK:
+ emit receivedata((void*)buffer);
+ if (-1 != fd)
+ write(fd,buffer,blocksize);
+ emit senddata((void*)buffer); /* fft :-) */
+ break;
+ }
+}
+
+#endif /* SOUNDCARD_H */
diff --git a/oss.h b/oss.h
new file mode 100644
index 0000000..1cb128b
--- /dev/null
+++ b/oss.h
@@ -0,0 +1,68 @@
+#ifndef OSS_H
+#define OSS_H
+
+#include <qobject.h>
+#include <qsocketnotifier.h>
+
+/* ---------------------------------------------------------------------- */
+
+#define STATUS_CLOSED 0
+#define STATUS_RECORD 1
+#define STATUS_PLAYBACK 2
+
+class Soundcard : public QObject
+{
+ Q_OBJECT;
+
+private:
+ /* sound card capabilities */
+ char devname[32];
+ int init_done;
+ int afmt_hw;
+ int afmt_sw;
+ int channels_hw;
+
+ int trigger;
+ char driver_name[64];
+
+ /* current settings */
+ int afmt;
+ int channels;
+ int rate;
+ int blocksize;
+ int latency;
+
+ /* file handle, reference count */
+ int fd, stat;
+ char buffer[65536];
+ QSocketNotifier *telmi;
+
+ /* internal functions */
+ void get_capabilities();
+ int open_dev(int record);
+ void close_dev();
+
+public:
+ Soundcard(const char *dev);
+ ~Soundcard();
+ char *driver();
+ void setparams(struct SOUNDPARAMS *params);
+ int start_record();
+ int start_playback();
+ int kill_buffer();
+ int stop();
+
+ int has_channels(); /* # of channels (1=mono,2=stereo) */
+ int has_format(int f); /* check format availibity */
+
+public slots:
+ void sounddata(int);
+
+signals:
+ void senddata(void *data);
+ /* !!! only one should be connected to receivedata !!! */
+ void receivedata(void *data);
+ void newparams(struct SOUNDPARAMS *params);
+};
+
+#endif
diff --git a/sound.cpp b/sound.cpp
new file mode 100644
index 0000000..5bdcc95
--- /dev/null
+++ b/sound.cpp
@@ -0,0 +1,272 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <qobject.h>
+#include <qlabel.h>
+#include <qsocketnotifier.h>
+#include <qgroupbox.h>
+#include <qwidget.h>
+
+#include <kapp.h>
+#include <kconfig.h>
+#include <klocale.h>
+
+#include "sound.moc"
+
+#define SPACE 12
+
+extern KApplication *globalKapp;
+extern KLocale *globalKlocale;
+
+/* ------------------------------------------------------------------------ */
+
+SoundOptions::SoundOptions(Soundcard *c, const char *name)
+ : KMainWindow(0,name,WType_TopLevel)
+{
+ static char *rates[] = {
+ "8000","11025","16000","22050","32000","44100","48000",NULL};
+ static char *triggers[] = {
+ "0","2","5","8","12","20",NULL};
+ unsigned int x,y,w,h,j;
+ int i;
+ QSize size;
+
+ card = c;
+
+ /* create widgets */
+ QGroupBox *f1 = new QGroupBox(this);
+ f1->setTitle(card->driver());
+
+ tab[0][0] = new QLabel(i18n("audio format"), this, "lformat");
+ tab[0][1] = new QLabel(i18n("# of channels"), this, "lchannels");
+ tab[0][2] = new QLabel(i18n("sample rate"), this, "lrate");
+ tab[0][3] = new QLabel(i18n("record trigger"), this, "ltrigger");
+
+ tab[1][0] = format = new QComboBox(FALSE, this, "format");
+ for (i = 1; i <= FMT_MAX; i <<= 1)
+ if (1 == card->has_format(i))
+ format->insertItem(sndfmt2str(i),-1);
+
+ tab[1][1] = channels = new QComboBox(FALSE, this, "channels");
+ channels->insertItem(i18n("mono"),-1);
+ if (2 == card->has_channels())
+ channels->insertItem(i18n("stereo"),-1);
+
+ tab[1][2] = rate = new QComboBox(TRUE, this, "rate");
+ for (i = 0; rates[i] != NULL; i++)
+ rate->insertItem(rates[i],-1);
+
+ tab[1][3] = trigger = new QComboBox(TRUE, this, "trigger");
+ for (i = 0; triggers[i] != NULL; i++)
+ trigger->insertItem(triggers[i],-1);
+
+ /* init */
+ for (j = 0; j < sizeof(tabw)/sizeof(int); j++) tabw[j] = 0;
+ for (j = 0; j < sizeof(tabh)/sizeof(int); j++) tabh[j] = 0;
+
+ /* get max sizes for cols/rows */
+ for (y = 0; y < sizeof(tabh)/sizeof(int); y++) {
+ for (x = 0; x < sizeof(tabw)/sizeof(int); x++) {
+ size = tab[x][y]->sizeHint();
+ if (size.isValid()) {
+ if (size.width() > tabw[x])
+ tabw[x] = size.width();
+ if (size.height() > tabh[y])
+ tabh[y] = size.height();
+ }
+ }
+ }
+
+ /* arrange widgets */
+ for (j = 0; j < sizeof(tabw)/sizeof(int); j++) tabw[j] += SPACE;
+ for (y = 0, h = 3*SPACE; y < sizeof(tabh)/sizeof(int); y++, h+=SPACE) {
+ for (x = 0, w = 2*SPACE; x < sizeof(tabw)/sizeof(int); x++, w+=SPACE) {
+ tab[x][y]->setGeometry(w,h,tabw[x],tabh[y]);
+ w += tabw[x];
+ }
+ h += tabh[y];
+ }
+ h += SPACE;
+ w += SPACE;
+
+ /* set frame size */
+ f1->setLineWidth(1);
+ f1->setFrameStyle(QFrame::Box | QFrame::Sunken);
+ f1->setGeometry(SPACE,SPACE,w-2*SPACE,h-2*SPACE);
+ h += SPACE;
+
+ /* add buttons */
+ cancel = new QPushButton(i18n("Cancel"), this, "cancel");
+ cancel->resize(cancel->sizeHint());
+ x = w-SPACE-cancel->width();
+ cancel->move(x,h);
+
+ apply = new QPushButton(i18n("Apply"), this, "apply");
+ apply->resize(apply->sizeHint());
+ x -= SPACE+apply->width();
+ apply->move(x,h);
+
+ ok = new QPushButton(i18n("OK"), this, "ok");
+ ok->resize(ok->sizeHint());
+ x -= SPACE+ok->width();
+ ok->move(x,h);
+ ok->setDefault(TRUE);
+
+ h += ok->height()+SPACE;
+
+ connect(ok, SIGNAL(clicked()), this, SLOT(ok_cb()));
+ connect(apply, SIGNAL(clicked()), this, SLOT(apply_cb()));
+ connect(cancel, SIGNAL(clicked()), this, SLOT(cancel_cb()));
+
+ connect(card,SIGNAL(newparams(struct SOUNDPARAMS*)),
+ this, SLOT(new_params(struct SOUNDPARAMS*)));
+
+ setCaption(i18n("sound options"));
+
+#if 1
+ /* session management */
+ i = -1;
+ if (globalKapp->isRestored()) {
+ for (i = 1; canBeRestored(i); i++)
+ if (0 == strcmp(classNameOfToplevel(i),"SoundOptions"))
+ break;
+ if (!canBeRestored(i))
+ i = -1;
+ }
+ if (i > 0) {
+ restore(i);
+ } else {
+ resize(w,h);
+ }
+#else
+ resize(w,h);
+#endif
+}
+
+void
+SoundOptions::ok_cb()
+{
+ set_params();
+ hide();
+}
+
+void
+SoundOptions::apply_cb()
+{
+ set_params();
+}
+
+void
+SoundOptions::cancel_cb()
+{
+ hide();
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct {
+ int fmt;
+ char *name;
+} fmt2str_map [] = {
+ { FMT_UNDEFINED, "UNDEFINED" },
+ { FMT_8BIT, "8bit pcm" },
+ { FMT_16BIT, "16bit pcm" },
+ { FMT_MULAW, "u-law" },
+ { FMT_ALAW, "a-law" },
+ { 0, NULL }
+};
+
+void
+SoundOptions::saveProperties(KConfig *config)
+{
+ config->writeEntry("rate",current.rate);
+ config->writeEntry("channels",current.channels);
+ config->writeEntry("format",current.format);
+ config->writeEntry("trigger",atoi(trigger->currentText()));
+}
+
+void
+SoundOptions::set_soundparam(int ra, int channels, int format, int tr)
+{
+ int i;
+ char text[32];
+
+ current.rate = ra;
+ current.channels = channels;
+ current.format = format;
+ card->setparams(&current);
+
+ sprintf(text,"%d",tr);
+ for (i = 0; i < trigger->count(); i++)
+ if (0 == strcmp(text,rate->text(i))) {
+ trigger->setCurrentItem(i);
+ return;
+ }
+ trigger->insertItem(text,i);
+}
+
+void
+SoundOptions::readProperties(KConfig *config)
+{
+ int rate, channels, format, trigger;
+
+ rate = atoi(config->readEntry("rate"));
+ channels = atoi(config->readEntry("channels"));
+ format = atoi(config->readEntry("format"));
+ trigger = atoi(config->readEntry("trigger"));
+ set_soundparam(rate,channels,format,trigger);
+}
+
+void
+SoundOptions::set_params()
+{
+ int i;
+ const char *text;
+
+ if (0 < (i = atoi(rate->currentText())))
+ current.rate = i;
+ current.channels = channels->currentItem()+1;
+ text = format->text(format->currentItem());
+ for (i = 0; fmt2str_map[i].name != NULL; i++)
+ if (0 == strcmp(fmt2str_map[i].name,text))
+ current.format = i;
+ card->setparams(&current);
+ emit set_level(atoi(trigger->currentText()));
+}
+
+void
+SoundOptions::new_params(struct SOUNDPARAMS *p)
+{
+ int i;
+ char text[32],*h;
+
+ h = sndfmt2str(current.format);
+ for (i = 0; i < format->count(); i++)
+ if (0 == strcmp(h,format->text(i)))
+ format->setCurrentItem(i);
+
+ memcpy(&current,p,sizeof(struct SOUNDPARAMS));
+ channels->setCurrentItem(current.channels-1);
+
+ sprintf(text,"%d",current.rate);
+ for (i = 0; i < rate->count(); i++)
+ if (0 == strcmp(text,rate->text(i))) {
+ rate->setCurrentItem(i);
+ return;
+ }
+ rate->insertItem(text,i);
+ rate->setCurrentItem(i);
+}
+
+char *sndfmt2str(int format)
+{
+ int i;
+
+ for (i = 0; fmt2str_map[i].name != NULL; i++)
+ if (fmt2str_map[i].fmt == format)
+ break;
+ return fmt2str_map[i].name;
+}
diff --git a/sound.h b/sound.h
new file mode 100644
index 0000000..b386c9f
--- /dev/null
+++ b/sound.h
@@ -0,0 +1,76 @@
+#ifndef SOUND_H
+#define SOUND_H
+
+#include <qdialog.h>
+#include <qwidget.h>
+#include <qcombobox.h>
+#include <qpushbutton.h>
+
+#include <kmainwindow.h>
+
+#ifdef HAVE_SYS_SOUNDCARD_H
+# include "oss.h"
+#endif
+#ifdef HAVE_SUN_AUDIOIO_H
+# include "sunaudio.h"
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+#define FMT_UNDEFINED 0
+#define FMT_8BIT 1 /* unsigned */
+#define FMT_16BIT 2 /* signed - native byte order */
+#define FMT_MULAW 4 /* NOT SUPPORTED (yet) */
+#define FMT_ALAW 8 /* NOT SUPPORTED (yet) */
+
+#define FMT_MAX 2
+
+struct SOUNDPARAMS {
+ int format;
+ int channels;
+ int rate;
+ int blocksize;
+ int latency;
+};
+
+char *sndfmt2str(int format);
+
+/* ---------------------------------------------------------------------- */
+
+class SoundOptions : public KMainWindow /* QDialog */
+{
+ Q_OBJECT;
+
+public:
+ SoundOptions(Soundcard *c, const char *name=0);
+ void set_soundparam(int rate, int channels, int format, int trigger);
+ virtual void saveProperties(KConfig *config);
+ virtual void readProperties(KConfig *config);
+
+private:
+ Soundcard *card;
+ struct SOUNDPARAMS current;
+ void set_params();
+
+ QComboBox *format;
+ QComboBox *channels;
+ QComboBox *rate;
+ QComboBox *trigger;
+
+ QWidget *tab[2][4];
+ int tabw[2],tabh[4];
+
+ QPushButton *ok, *apply, *cancel;
+
+signals:
+ void set_level(int l);
+
+public slots:
+ void new_params(struct SOUNDPARAMS *p);
+ void ok_cb();
+ void apply_cb();
+ void cancel_cb();
+};
+
+#endif
+
diff --git a/soundfft.c b/soundfft.c
new file mode 100644
index 0000000..b1a7f81
--- /dev/null
+++ b/soundfft.c
@@ -0,0 +1,196 @@
+/*
+ * Program: REALFFT.C
+ * Author: Philip VanBaren
+ * Date: 2 September 1993
+ *
+ * Description: These routines perform an FFT on real data.
+ * On a 486/33 compiled using Borland C++ 3.1 with full
+ * speed optimization and a small memory model, a 1024 point
+ * FFT takes about 16ms.
+ * This code is for integer data, but could be converted
+ * to float or double simply by changing the data types
+ * and getting rid of the bit-shifting necessary to prevent
+ * overflow/underflow in fixed-point calculations.
+ *
+ * Note: Output is BIT-REVERSED! so you must use the BitReversed to
+ * get legible output, (i.e. Real_i = buffer[ BitReversed[i] ]
+ * Imag_i = buffer[ BitReversed[i]+1 ] )
+ * Input is in normal order.
+ *
+ * Copyright (C) 1995 Philip VanBaren
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * For Borland C++/TASM you may define the flag ASMFFTCODE in order to use the
+ * assembly code for the FFT. This will skip the compilation of the C version
+ * of the routine; you must be sure to add realffta.asm to your makefile
+ */
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "soundfft.h"
+
+int *BitReversed;
+short *SinTable;
+int Points = 0;
+
+/*
+ * Initialize the Sine table and Twiddle pointers (bit-reversed pointers)
+ * for the FFT routine.
+ */
+void InitializeFFT(int fftlen)
+{
+ int i;
+ int temp;
+ int mask;
+
+ /*
+ * FFT size is only half the number of data points
+ * The full FFT output can be reconstructed from this FFT's output.
+ * (This optimization can be made since the data is real.)
+ */
+ Points = fftlen;
+
+ if((SinTable=(short *)malloc(Points*sizeof(short)))==NULL)
+ {
+ puts("Error allocating memory for Sine table.");
+ exit(1);
+ }
+ if((BitReversed=(int *)malloc(Points/2*sizeof(int)))==NULL)
+ {
+ puts("Error allocating memory for BitReversed.");
+ exit(1);
+ }
+
+ for(i=0;i<Points/2;i++)
+ {
+ temp=0;
+ for(mask=Points/4;mask>0;mask >>= 1)
+ temp=(temp >> 1) + (i&mask ? Points/2 : 0);
+
+ BitReversed[i]=temp;
+ }
+
+ for(i=0;i<Points/2;i++)
+ {
+ register double s,c;
+ s=floor(-32768.0*sin(2*M_PI*i/(Points))+0.5);
+ c=floor(-32768.0*cos(2*M_PI*i/(Points))+0.5);
+ if(s>32767.5) s=32767;
+ if(c>32767.5) c=32767;
+ SinTable[BitReversed[i] ]=(short)s;
+ SinTable[BitReversed[i]+1]=(short)c;
+ }
+}
+
+/*
+ * Free up the memory allotted for Sin table and Twiddle Pointers
+ */
+void EndFFT(void)
+{
+ free(BitReversed);
+ free(SinTable);
+ Points=0;
+}
+
+/* Include this only if you don't use the REALFFTA.ASM routine */
+/* This is for DOS only */
+
+#ifndef ASMFFTCODE
+
+short *A,*B;
+short *sptr;
+short *endptr1,*endptr2;
+int *br1,*br2;
+long HRplus,HRminus,HIplus,HIminus;
+
+/*
+ * Actual FFT routine. Must call InitializeFFT(fftlen) first!
+ */
+void RealFFT(short *buffer)
+{
+ int ButterfliesPerGroup=Points/4;
+
+ endptr1=buffer+Points;
+
+ /*
+ * Butterfly:
+ * Ain-----Aout
+ * \ /
+ * / \
+ * Bin-----Bout
+ */
+
+ while(ButterfliesPerGroup>0)
+ {
+ A=buffer;
+ B=buffer+ButterfliesPerGroup*2;
+ sptr=SinTable;
+
+ while(A<endptr1)
+ {
+ register short sin=*sptr;
+ register short cos=*(sptr+1);
+ endptr2=B;
+ while(A<endptr2)
+ {
+ long v1=((long)*B*cos + (long)*(B+1)*sin) >> 15;
+ long v2=((long)*B*sin - (long)*(B+1)*cos) >> 15;
+ *B=(*A+v1)>>1;
+ *(A++)=*(B++)-v1;
+ *B=(*A-v2)>>1;
+ *(A++)=*(B++)+v2;
+ }
+ A=B;
+ B+=ButterfliesPerGroup*2;
+ sptr+=2;
+ }
+ ButterfliesPerGroup >>= 1;
+ }
+ /*
+ * Massage output to get the output for a real input sequence.
+ */
+ br1=BitReversed+1;
+ br2=BitReversed+Points/2-1;
+
+ while(br1<=br2)
+ {
+ register long temp1,temp2;
+ short sin=SinTable[*br1];
+ short cos=SinTable[*br1+1];
+ A=buffer+*br1;
+ B=buffer+*br2;
+ HRplus = (HRminus = *A - *B ) + (*B << 1);
+ HIplus = (HIminus = *(A+1) - *(B+1)) + (*(B+1) << 1);
+ temp1 = ((long)sin*HRminus - (long)cos*HIplus) >> 15;
+ temp2 = ((long)cos*HRminus + (long)sin*HIplus) >> 15;
+ *B = (*A = (HRplus + temp1) >> 1) - temp1;
+ *(B+1) = (*(A+1) = (HIminus + temp2) >> 1) - HIminus;
+
+ br1++;
+ br2--;
+ }
+ /*
+ * Handle DC bin separately
+ */
+ buffer[0]+=buffer[1];
+ buffer[1]=0;
+}
+#endif
+
diff --git a/soundfft.h b/soundfft.h
new file mode 100644
index 0000000..126b623
--- /dev/null
+++ b/soundfft.h
@@ -0,0 +1,5 @@
+/* soundfft.c */
+extern int *BitReversed;
+extern void InitializeFFT(int fftlen);
+extern void EndFFT(void);
+extern void RealFFT(short *buffer);
diff --git a/sunaudio.cpp b/sunaudio.cpp
new file mode 100644
index 0000000..d1c3b97
--- /dev/null
+++ b/sunaudio.cpp
@@ -0,0 +1,369 @@
+#ifdef HAVE_SUN_AUDIOIO_H
+
+/*
+ * Soundcard class for Solaris
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+
+/* for ioctl(I_FLUSH) */
+#include <stropts.h>
+#include <sys/conf.h>
+
+#include "sound.h"
+#include "sunaudio.h"
+#include "sunaudio.moc"
+
+/* ---------------------------------------------------------------------- */
+
+Soundcard::Soundcard(char *dev)
+{
+ if (dev)
+ strcpy(devname,dev);
+ else
+ strcpy(devname,"/dev/audio");
+
+ strcpy(driver_name,"sunaudio");
+ get_capabilities();
+ channels = 1;
+ rate = 16000;
+ afmt = AUDIO_ENCODING_LINEAR;
+ precision = 16;
+ fd = -1;
+ stat = STATUS_CLOSED;
+}
+
+Soundcard::~Soundcard()
+{
+ stop();
+}
+
+int
+Soundcard::start_record()
+{
+ switch (stat) {
+ case STATUS_CLOSED:
+ if (!init_done)
+ get_capabilities();
+ if (!init_done)
+ return -1;
+ return open_dev(TRUE);
+ case STATUS_RECORD:
+ return 0;
+ case STATUS_PLAYBACK:
+ close_dev();
+ return open_dev(TRUE);
+ }
+ return -1;
+}
+
+int
+Soundcard::start_playback()
+{
+ switch (stat) {
+ case STATUS_CLOSED:
+ if (!init_done)
+ get_capabilities();
+ if (!init_done)
+ return -1;
+ return open_dev(FALSE);
+ case STATUS_RECORD:
+ close_dev();
+ return open_dev(FALSE);
+ case STATUS_PLAYBACK:
+ return 0;
+ }
+ return -1;
+}
+
+int
+Soundcard::kill_buffer()
+{
+ if(ioctl(fd, I_FLUSH, FLUSHRW)<0) {
+ fprintf(stderr,"Sun audio error: could not flush queues: %s\n",strerror(errno));
+ return errno;
+ }
+ return 0;
+}
+
+int
+Soundcard::stop()
+{
+ if (stat != STATUS_CLOSED)
+ close_dev();
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+Soundcard::get_capabilities()
+{
+ int dsp;
+ audio_device_t audiotype;
+
+ if (-1 != (dsp = open(devname, O_RDONLY))) {
+
+ afmt_hw = -1;
+ if (ioctl(dsp,AUDIO_GETDEV,&audiotype)<0) {
+ fprintf(stderr,"Sun driver warning: could not determine audio device type\n");
+ } else {
+ sprintf(driver_name,audiotype.name);
+ DEBUG(printf("Sound driver recognized as ,,%s''\n",driver_name));
+ }
+ if (!strcmp(audiotype.name,"SUNW,am79c30")) {
+ // AMD 79C30
+ // 8bit mono ulaw 8kHz
+ channels_hw = 1;
+ afmt_hw = AUDIO_ENCODING_ULAW;
+ precision_hw = 8;
+ rate_hw = 8000;
+ /*
+ // if (tmp_precision==8)&&(tmp_channel==1)&&(tmp_rate==8000))
+ tmp_encoding=AUDIO_ENCODING_ULAW;
+ fprintf(stderr,"ERROR: this program needs better soundcard\n");
+ else {
+ fprintf(stderr,"Sound init error");
+ return 1;
+ }
+ */
+ } else
+ if ((!strcmp(audiotype.name,"SUNW,CS4231"))||
+ (!strcmp(audiotype.name,"SUNW,dbri"))||
+ (!strcmp(audiotype.name,"speakerbox"))) {
+ // CS 4231 or DBRI or speaker box
+ // 16bit mono/stereo linear 8kHz - 48kHz
+ channels_hw = 2;
+ afmt_hw = AUDIO_ENCODING_LINEAR | AUDIO_ENCODING_ULAW;
+ precision_hw = 16 | 8;
+ rate_hw = 48000;
+ /*
+ if(tmp_precision==16)
+ tmp_encoding=AUDIO_ENCODING_LINEAR;
+ // 8bit mono ulaw 8kHz - 48kHz
+ else if((tmp_precision==8)&&(tmp_channels==1))
+ tmp_encoding=AUDIO_ENCODING_ULAW;
+ else {
+ fprintf(stderr,"Sound init error");
+ return 1;
+ }
+ */
+ }
+
+ if(afmt_hw==-1) {
+ // if((tmp_precision==8)&&(tmp_stereo==1)&&(tmp_rate<=8000))
+ // play_encoding = AUDIO_ENCODING_ULAW;
+ // else
+ channels_hw = 2;
+ precision_hw = 16 | 8;
+ afmt_hw = AUDIO_ENCODING_LINEAR | AUDIO_ENCODING_ULAW;
+ rate_hw = 48000;
+ }
+ close(dsp);
+ init_done = 1;
+
+ } else {
+ init_done = 0;
+ }
+}
+
+int
+Soundcard::has_channels()
+{
+ if (!init_done)
+ return -1;
+ return channels_hw;
+}
+
+int
+Soundcard::has_format(int f)
+{
+ if (!init_done)
+ return -1;
+ switch (f) {
+ case FMT_8BIT:
+ return (afmt_hw & AUDIO_ENCODING_ULAW) ? 1 : 0;
+ break;
+ case FMT_16BIT:
+ return (afmt_hw & AUDIO_ENCODING_LINEAR) ? 1 : 0;
+ case FMT_MULAW:
+ case FMT_ALAW:
+ default:
+ return 0;
+ }
+}
+
+char*
+Soundcard::driver()
+{
+ return driver_name;
+}
+
+int
+Soundcard::open_dev(int record)
+{
+ struct SOUNDPARAMS p;
+ struct audio_info_t audioinfo;
+ struct audio_prinfo_t *audiotype;
+
+ if (-1 == (fd = open(devname,record ? O_RDONLY : O_WRONLY)))
+ goto err;
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+
+ AUDIO_INITINFO(&audioinfo);
+
+ audiotype = record ? &audioinfo.record : &audioinfo.play;
+
+ audiotype->precision = precision;
+ audiotype->channels = channels;
+ audiotype->sample_rate = rate;
+ audiotype->encoding = afmt;
+
+ if (record)
+ audiotype->port=AUDIO_MICROPHONE; // could be AUDIO_LINE_IN
+
+ if(ioctl(fd,AUDIO_SETINFO,&audioinfo)<0) {
+ fprintf(stderr,"Sun audio error: could not set info: %s\n",strerror(errno));
+ goto err;
+ }
+
+ if(ioctl(fd,AUDIO_GETINFO,&audioinfo)<0) {
+ fprintf(stderr,"Sun audio error: could not get info: %s\n",strerror(errno));
+ goto err;
+ }
+
+ if ((audiotype->precision != (uint_t)precision) ||
+ (audiotype->channels != (uint_t)channels) ||
+ (audiotype->sample_rate != (uint_t)rate) ||
+ (audiotype->encoding != (uint_t)afmt)) {
+ fprintf(stderr,"Sun audio error: could not set info properly\n");
+ goto err;
+ }
+
+ /* If would not flush, data has MAX input level at 99 */
+ if(ioctl(fd, I_FLUSH, FLUSHRW)<0) {
+ fprintf(stderr,"Sun audio error: could not flush queues: %s\n",strerror(errno));
+ goto err;
+ }
+
+ telmi = new QSocketNotifier(fd, record ? QSocketNotifier::Read : QSocketNotifier::Write);
+ QObject::connect(telmi,SIGNAL(activated(int)), this, SLOT(sounddata(int)));
+
+ stat = record ? STATUS_RECORD : STATUS_PLAYBACK;
+
+ p.channels = audiotype->channels;
+ p.rate = audiotype->sample_rate;
+ p.blocksize = audiotype->buffer_size;
+
+ p.latency = p.blocksize * 1000 / p.channels / p.rate;
+
+ switch (afmt)
+ {
+ case AUDIO_ENCODING_ULAW:
+ p.format = FMT_8BIT;
+ break;
+ case AUDIO_ENCODING_LINEAR:
+ p.latency /= 2;
+ p.format = FMT_16BIT;
+ break;
+ default:
+ fprintf(stderr,"oops(open): unsupported sound format\n");
+ exit(1);
+ }
+
+ emit newparams(&p);
+
+ rate = p.rate;
+ channels = p.channels;
+ blocksize = p.blocksize;
+ latency = p.latency;
+
+ DEBUG(printf("%s (format=%d, %s, rate=%d, blocksize=%d, latency=%d ms)\n",
+ record ? "recording" : "playback",
+ afmt, (p.channels == 2) ? "stereo" : "mono",
+ p.rate, p.blocksize, p.latency));
+ DEBUG(printf("Sound driver opened\n"));
+
+ return 0;
+
+err:
+ if (-1 != fd)
+ close(fd);
+ stat = STATUS_CLOSED;
+ fd = -1;
+ return -1;
+}
+
+void
+Soundcard::close_dev()
+{
+ close(fd);
+ fd = -1;
+ stat = STATUS_CLOSED;
+
+ if (telmi) {
+ delete telmi;
+ telmi = NULL;
+ }
+ DEBUG(printf("Sound driver closed\n"));
+ return;
+}
+
+void
+Soundcard::setparams(struct SOUNDPARAMS *p)
+{
+
+ rate = p->rate;
+ channels = p->channels;
+ switch (p->format) {
+ case FMT_8BIT: afmt = AUDIO_ENCODING_ULAW; break;
+ case FMT_16BIT: afmt = AUDIO_ENCODING_LINEAR; break;
+ default: fprintf(stderr,"oops(set): unsupported sound %d format\n",p->format); exit(1);
+ }
+
+ switch (stat) {
+ case STATUS_RECORD:
+ close_dev();
+ open_dev(TRUE);
+ break;
+ case STATUS_PLAYBACK:
+ close_dev();
+ open_dev(FALSE);
+ break;
+ case STATUS_CLOSED:
+ if (!init_done)
+ get_capabilities();
+ if (!init_done)
+ return;
+ if (0 == open_dev(TRUE))
+ close_dev();
+ break;
+ }
+}
+
+void
+Soundcard::sounddata(int s)
+{
+ switch (stat) {
+ case STATUS_RECORD:
+ read(fd,buffer,blocksize);
+ emit senddata((void*)buffer);
+ break;
+ case STATUS_PLAYBACK:
+ emit receivedata((void*)buffer);
+ write(fd,buffer,blocksize);
+ emit senddata((void*)buffer); /* fft :-) */
+ break;
+ }
+}
+
+#endif
+
diff --git a/sunaudio.h b/sunaudio.h
new file mode 100644
index 0000000..14a369f
--- /dev/null
+++ b/sunaudio.h
@@ -0,0 +1,68 @@
+#ifndef SUNAUDIO_H
+#define SUNAUDIO_H
+
+#include <qobject.h>
+#include <qsocknot.h>
+
+/* ---------------------------------------------------------------------- */
+
+#define STATUS_CLOSED 0
+#define STATUS_RECORD 1
+#define STATUS_PLAYBACK 2
+
+class Soundcard : public QObject
+{
+ Q_OBJECT;
+
+private:
+ /* sound card capabilities */
+ char devname[32];
+ char driver_name[64];
+ int init_done;
+ int afmt_hw, rate_hw, precision_hw, channels_hw;
+
+ /* current settings */
+ /* XXX */
+ int precision;
+ int afmt; /* Encoding (U-LAW, A-LAW - precision 12-bit, 8kHz rate) PCM - 16-bit */
+ int channels; /* Channels - stereo? */
+ int rate; /* Sample rate samples per second */
+ int blocksize;
+ int latency;
+
+ /* file handle, reference count */
+ int fd, stat;
+ char buffer[65536];
+ QSocketNotifier *telmi;
+
+ /* internal functions */
+ void get_capabilities();
+ int open_dev(int record);
+ void close_dev();
+
+public:
+ Soundcard(char *dev);
+ ~Soundcard();
+ char *driver();
+ void setparams(struct SOUNDPARAMS *params);
+ int start_record();
+ int start_playback();
+ int kill_buffer();
+ int stop();
+
+ int has_channels(); /* # of channels (1=mono,2=stereo) */
+ int has_format(int f); /* check format availibity */
+
+ int get_blocksize() { return blocksize;};
+
+public slots:
+ void sounddata(int);
+
+signals:
+ void senddata(void *data);
+ /* !!! only one should be connected to receivedata !!! */
+ void receivedata(void *data);
+ void newparams(struct SOUNDPARAMS *params);
+};
+
+#endif