diff options
author | kraxel <kraxel> | 2004-11-03 12:53:56 +0000 |
---|---|---|
committer | kraxel <kraxel> | 2004-11-03 12:53:56 +0000 |
commit | f172e7016a13f435adfbb93a85d5e22b2237a377 (patch) | |
tree | 98e523f7d77185af2a7092f2421eb35b1a976073 | |
parent | a811c9986cad3e9a5c108fceb760e7e76a7dd65a (diff) | |
download | krecord-f172e7016a13f435adfbb93a85d5e22b2237a377.tar.gz |
- add files.
-rw-r--r-- | GNUmakefile | 153 | ||||
-rw-r--r-- | INSTALL | 59 | ||||
-rw-r--r-- | buffer.cpp | 841 | ||||
-rw-r--r-- | buffer.h | 192 | ||||
-rw-r--r-- | byteorder.h | 49 | ||||
-rw-r--r-- | fft.cpp | 218 | ||||
-rw-r--r-- | fft.h | 40 | ||||
-rwxr-xr-x | img/1rightarrow.png | bin | 0 -> 431 bytes | |||
-rwxr-xr-x | img/2leftarrow.png | bin | 0 -> 740 bytes | |||
-rwxr-xr-x | img/2rightarrow.png | bin | 0 -> 418 bytes | |||
-rwxr-xr-x | img/freq.png | bin | 0 -> 362 bytes | |||
-rwxr-xr-x | img/level.png | bin | 0 -> 497 bytes | |||
-rwxr-xr-x | img/line_monitor.png | bin | 0 -> 733 bytes | |||
-rwxr-xr-x | img/mrecord.png | bin | 0 -> 389 bytes | |||
-rwxr-xr-x | img/player_stop.png | bin | 0 -> 345 bytes | |||
-rw-r--r-- | index.html | 118 | ||||
-rw-r--r-- | krecord.cpp | 658 | ||||
-rw-r--r-- | krecord.h | 122 | ||||
-rw-r--r-- | level.cpp | 438 | ||||
-rw-r--r-- | level.h | 56 | ||||
-rw-r--r-- | mix.c | 75 | ||||
-rw-r--r-- | mk/Autoconf.mk | 138 | ||||
-rw-r--r-- | mk/Compile.mk | 88 | ||||
-rw-r--r-- | mk/Maintainer.mk | 12 | ||||
-rw-r--r-- | mk/Variables.mk | 46 | ||||
-rw-r--r-- | oss.cpp | 358 | ||||
-rw-r--r-- | oss.h | 68 | ||||
-rw-r--r-- | sound.cpp | 272 | ||||
-rw-r--r-- | sound.h | 76 | ||||
-rw-r--r-- | soundfft.c | 196 | ||||
-rw-r--r-- | soundfft.h | 5 | ||||
-rw-r--r-- | sunaudio.cpp | 369 | ||||
-rw-r--r-- | sunaudio.h | 68 |
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) @@ -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(¶ms,0,sizeof(struct SOUNDPARAMS)); + sprintf(bufname,"buffer #%d",++counter); + buffers = (char**)malloc(sizeof(void*)); + buffers[0] = (char*)malloc(BUFFER_SIZE); + bufcount = 1; + position = 0; +} + +RAMBuffer::~RAMBuffer() +{ + int i; + + for (i = 0; i < bufcount; i++) { + free(buffers[i]); + } + free(buffers); +} + +int +RAMBuffer::start_write(struct SOUNDPARAMS *p) +{ + memcpy(¶ms,p,sizeof(struct SOUNDPARAMS)); + return 0; +} + +void RAMBuffer::stop_write() {}; + +struct SOUNDPARAMS* +RAMBuffer::get_params() +{ + return ¶ms; +} + +int +RAMBuffer::get_size() +{ + return size; +} + +char* +RAMBuffer::name() +{ + return bufname; +} + +void* +RAMBuffer::read_audio(int len) +{ + /* FIXME (?): one can't read over buffer boundaries */ + char *ptr; + + if (position+len > size) + return NULL; + ptr = buffers[position>>BUFFER_SHIFT]; + ptr += (position&BUFFER_MASK); + position += len; + return ptr; +} + +int +RAMBuffer::write_audio(int len, void *data) +{ + /* dito for write - not over buffer boundaries */ + if ((position>>BUFFER_SHIFT) >= bufcount) { + buffers = (char**)realloc(buffers,sizeof(void*)*(bufcount+1)); + if (NULL == (buffers[bufcount] = (char*)malloc(BUFFER_SIZE))) + return -1; + bufcount++; + } + memcpy(buffers[position>>BUFFER_SHIFT]+(position&BUFFER_MASK), data, len); + + position += len; + if (size < position) + size = position; + return 0; +} + +int +RAMBuffer::seek(int pos) +{ + if (pos > size) + return -1; + position = pos; + return position; +} + +int +RAMBuffer::tell() +{ + return position; +} + +/* ---------------------------------------------------------------------- */ + +FileBuffer::FileBuffer() +{ + memset(¶ms,0,sizeof(struct SOUNDPARAMS)); + fd = -1; + size = position = 0; + bstart = bstop = 0; +} + +FileBuffer::~FileBuffer() +{ + if (fd == -1) + return; + close(fd); +} + +void +FileBuffer::init_header() +{ + /* stolen from cdda2wav */ + int nBitsPerSample = 8; + if (params.format == FMT_16BIT) + nBitsPerSample = 16; + + unsigned long nBlockAlign = params.channels * ((nBitsPerSample + 7) / 8); + unsigned long nAvgBytesPerSec = nBlockAlign * params.rate; + unsigned long temp = /* data length */ 0 + + sizeof(WAVEHDR) - sizeof(CHUNKHDR); + + fileheader.chkRiff.ckid = cpu_to_le32(FOURCC_RIFF); + fileheader.fccWave = cpu_to_le32(FOURCC_WAVE); + fileheader.chkFmt.ckid = cpu_to_le32(FOURCC_FMT); + fileheader.chkFmt.dwSize = cpu_to_le32(16); + fileheader.wFormatTag = cpu_to_le16(WAVE_FORMAT_PCM); + fileheader.nChannels = cpu_to_le16(params.channels); + fileheader.nSamplesPerSec = cpu_to_le32(params.rate); + fileheader.nAvgBytesPerSec = cpu_to_le32(nAvgBytesPerSec); + fileheader.nBlockAlign = cpu_to_le16(nBlockAlign); + fileheader.wBitsPerSample = cpu_to_le16(nBitsPerSample); + fileheader.chkData.ckid = cpu_to_le32(FOURCC_DATA); + fileheader.chkRiff.dwSize = cpu_to_le32(temp); + fileheader.chkData.dwSize = cpu_to_le32(0 /* data length */); +} + +int +FileBuffer::attach(const char *file) +{ + int new_file = 0; + + if (-1 != fd) + close(fd); + + ro = 0; + position = 0; + size = 0; + offset = sizeof(WAVEHDR); + strcpy(filename,file); + + if (-1 == (fd = open(filename,O_RDWR))) { + if (errno == ENOENT) { + if (-1 == (fd = open(filename,O_RDWR|O_CREAT,0666))) { + xperror(i18n("can't create wav file")); + return -1; + } + new_file = 1; + } else { + if (-1 == (fd = open(filename,O_RDONLY))) { + xperror(i18n("can't open wav file")); + return -1; + } else + ro = 1; + } + } + fcntl(fd,F_SETFD,FD_CLOEXEC); + if (!new_file) { + read(fd,&fileheader,offset); + if (!IS_STD_WAV_HEADER(fileheader)) { +#if 0 /* nice for debugging, but annonying for everyday usage */ + KMessageBox::error(NULL, i18n("not a wav file")); +#endif + return -1; + } + if (le16_to_cpu(fileheader.wFormatTag) != WAVE_FORMAT_PCM) { + KMessageBox::error(NULL,i18n("unsupported audio format")); + return -1; + } + params.format = FMT_8BIT; + if (16 == le16_to_cpu(fileheader.wBitsPerSample)) + params.format = FMT_16BIT; + params.channels = le16_to_cpu(fileheader.nChannels); + params.rate = cpu_to_le32(fileheader.nSamplesPerSec); + size = le32_to_cpu(fileheader.chkData.dwSize); + } + return 0; +} + +int +FileBuffer::start_write(struct SOUNDPARAMS *p) +{ + memcpy(¶ms,p,sizeof(struct SOUNDPARAMS)); + init_header(); + lseek(fd,0,SEEK_SET); + write(fd,&fileheader,offset); + return 0; +} + +void +FileBuffer::stop_write() +{ + unsigned long temp = size + sizeof(WAVEHDR) - sizeof(CHUNKHDR); + + fileheader.chkRiff.dwSize = cpu_to_le32(temp); + fileheader.chkData.dwSize = cpu_to_le32(size); + lseek(fd,0,SEEK_SET); + write(fd,&fileheader,offset); +} + +struct SOUNDPARAMS* +FileBuffer::get_params() +{ + return ¶ms; +} + +int +FileBuffer::get_size() +{ + return size; +} + +char* +FileBuffer::name() +{ + return filename; +} + +void* +FileBuffer::read_audio(int len) +{ + int rc; + + if (position+len > size) + return NULL; + + /* printf("[%d - %d (+%d) - %d] / %d\n",bstart,position,len,bstop,size); */ + if (position < bstart || position+len > bstop) { + rc = read(fd,buffer,65536); +#if BYTE_ORDER == BIG_ENDIAN + if (params.format == FMT_16BIT) { + /* byteswap 16bit pcm on bigendian machines */ + int i; + char h; + for (i = 0; i < rc; i += 2) { + h = buffer[i]; + buffer[i] = buffer[i+1]; + buffer[i+1] = h; + } + } +#endif + if (-1 == rc) + return NULL; + bstart = position, bstop = position+rc; + } + position += len; + return buffer+position-bstart-len; +} + +int +FileBuffer::write_audio(int len, void *data) +{ + int rc; + +#if BYTE_ORDER == BIG_ENDIAN + char *buf; + int i; + + if (params.format == FMT_16BIT) { + /* byteswap 16bit pcm on bigendian machines */ + buf = (char*)malloc(len); + for (i = 0; i < len; i += 2) { + buf[i] = ((char*)data)[i+1]; + buf[i+1] = ((char*)data)[i]; + } + rc = write(fd,buf,len); + free(buf); + } else { + rc = write(fd,data,len); + } +#else + rc = write(fd,data,len); +#endif + if (len == rc) { + position += len; + if (position > size) + size = position; + return 0; + } else + return -1; +} + +int +FileBuffer::seek(int pos) +{ + if (pos > size) + return -1; + position = pos; + if (-1 == lseek(fd,position+offset,SEEK_SET)) + perror("fb: lseek"); + return 0; +} + +int +FileBuffer::tell() +{ + return position; +} + +/* ---------------------------------------------------------------------- */ + +BufferList::BufferList(QListBox *l, Soundcard *c) +{ + listbox = l; + card = c; + count = 0; + brecord = -1; + bplayback = -1; + mon = 0; + level = 0; + wait = 0; + new_buffer_count = 0; + + connect(card,SIGNAL(newparams(struct SOUNDPARAMS*)), + this, SLOT(new_params(struct SOUNDPARAMS*))); + connect(card,SIGNAL(senddata(void*)), + this, SLOT(new_data(void*))); + connect(card,SIGNAL(receivedata(void*)), + this, SLOT(post_data(void*))); +} + +int +BufferList::add_filebuffer(const char *filename) +{ + FileBuffer *fbuffer; + + fbuffer = new FileBuffer(); + if (-1 == fbuffer->attach(filename)) { + delete fbuffer; + return -1; + } + add_buffer(fbuffer); + return 0; +} + +int +BufferList::add_rambuffer() +{ + add_buffer(new RAMBuffer()); + return 0; +} + +void +BufferList::add_buffer(AudioBuffer *buf) +{ + if (count) + buffers = (AudioBuffer**)realloc + (buffers,sizeof(AudioBuffer*)*(count+1)); + else + buffers = (AudioBuffer**)malloc(sizeof(AudioBuffer*)); + buffers[count] = buf; + listbox->insertItem("*",count); + listbox->setCurrentItem(count); + label_buffer(count); + count++; +} + +void +BufferList::label_buffer(int buf) +{ + struct SOUNDPARAMS *p; + int sec,len,size; + char text[256]; + + p = buffers[buf]->get_params(); + if (p->format != FMT_UNDEFINED) { + size = buffers[buf]->get_size(); + sec = size/p->rate/p->channels; + if (p->format == FMT_16BIT) + sec /= 2; + len = sprintf(text,"%5d %s %s %d:%02d / ", + p->rate, + (p->channels == 1) + ? (const char*) i18n("mono") + : (const char*) i18n("stereo"), + sndfmt2str(p->format), + sec/60,sec%60); + if (size>>20) + len += sprintf(text+len,"%d.%dMB ", + size>>20,((size&0xfffff)*10)>>20); + else + len += sprintf(text+len,"%dkB ",size>>10); + } else { + strcpy(text,i18n("new")); + strcat(text," "); + } + strcat(text,buffers[buf]->name()); + if (p->format != FMT_UNDEFINED && buffers[buf]->clippedsamples) { + sprintf(text+strlen(text)," (%2d samples clipped)", + buffers[buf]->clippedsamples); + } + listbox->changeItem(text,buf); +} + +void +BufferList::del_buffer(int buf) +{ + int i; + + if (buffers[buf]->is_busy()) { + KMessageBox::error(NULL, i18n("buffer is busy")); + return; + } + listbox->removeItem(buf); + delete buffers[buf]; + for (i = buf+1; i < count; i++) + buffers[i-1] = buffers[i]; + count--; + if (0 == count) + free(buffers); +} + +void +BufferList::new_params(struct SOUNDPARAMS *p) +{ + memcpy(¶ms,p,sizeof(struct SOUNDPARAMS)); +} + +void +BufferList::new_data(void *data) +{ + static int count; + int i,j,max; + unsigned char *c; + short *s; + + int clippedhere=0; + + if (wait) { + if (params.format == FMT_16BIT) { + for (i = (params.blocksize>>1)-1, s=(short*)data, max = 0; i; i--) + if (abs(s[i]) > max) + max = abs(s[i]); + max = max * 100 / 32768; + } else { + for (i = params.blocksize-1, c=(unsigned char*)data, max = 0; i; i--) + if ((j = abs((int)c[i]-128)) > max) + max = j; + max = max * 100 / 128; + } + if (max >= level) { + wait = 0; + emit status(i18n("record")); + } else + return; + } + + /* we're here if we're not waiting */ + /* we have to check for possible clipping */ + if (params.format == FMT_16BIT) { + for (i = (params.blocksize>>1)-1, s=(short*)data, max = 0; i; i--) + if ((s[i]==32767) || (s[i]==-32768)) { + //fprintf(stderr,"!"); + clippedhere++; + } + max = max * 100 / 32768; + } else { + for (i = params.blocksize-1, c=(unsigned char*)data, max = 0; i; i--) { + j = abs((int)c[i]-128); + if ((j==127) || (j==-128)) { + //fprintf(stderr,"!"); + clippedhere++; + } + } + max = max * 100 / 128; + } + + //fprintf(stderr,"%d\n",clippedhere); + + if (-1 != brecord) { + buffers[brecord]->clippedsamples += clippedhere; + if (-1 == buffers[brecord]->write_audio(params.blocksize,data)) { + istop(); + xperror(i18n("can't save sound data")); + } + if (++count == 16) { + count = 0; + label_buffer(brecord); + set_pos_size(i18n("record"),brecord); + } + } +} + +void +BufferList::post_data(void *data) +{ + static int count = 0; + void *ptr; + + if (NULL == (ptr = buffers[bplayback]->read_audio(params.blocksize))) { + memset(data,0,params.blocksize); + istop(); + //fprintf(stderr,"->EOF"); + return; + } + memcpy(data,ptr,params.blocksize); + + if (++count == 16) { + count = 0; + set_pos_size(i18n("playback"),bplayback); + } +} + +void +BufferList::set_pos_size(const char *start, int buf) +{ + struct SOUNDPARAMS *p; + int sec,len,size; + char text[256]; + + p = buffers[buf]->get_params(); + + size = buffers[buf]->tell(); + sec = size/p->rate/p->channels; + if (p->format == FMT_16BIT) + sec /= 2; + len = sprintf(text,"%s %d:%02d / ",start,sec/60,sec%60); + + size = buffers[buf]->get_size(); + sec = size/p->rate/p->channels; + if (p->format == FMT_16BIT) + sec /= 2; + sprintf(text+len,"%d:%02d",sec/60,sec%60); + emit status(text); +} + +/* ---------------------------------------------------------------------- */ + +void +BufferList::new_ram() +{ + add_rambuffer(); +} + +void +BufferList::save_buf(const char *filename) +{ + int i; + void *data; + struct SOUNDPARAMS *p; + FileBuffer fbuf; + + if (-1 == (i = listbox->currentItem())) + return; + if (buffers[i]->is_busy()) { + KMessageBox::error(NULL, i18n("buffer is busy")); + return; + } + p = buffers[i]->get_params(); + buffers[i]->seek(0); + + if (-1 == fbuf.attach(filename)) + return; + fbuf.start_write(p); + while (NULL != (data = buffers[i]->read_audio(p->blocksize))) { + if (-1 == fbuf.write_audio(p->blocksize,data)) { + xperror(i18n("can't write sound data")); + break; + } + } + fbuf.stop_write(); +} + +void +BufferList::del_buf() +{ + int i; + + if (-1 != (i = listbox->currentItem())) + del_buffer(i); +} + +void +BufferList::monitor() +{ + mon = !mon; + if (mon) { + if (-1 == card->start_record()) { + xperror(i18n("can't open soundcard")); + return; + } + emit status(i18n("monitor")); + } else { + if (-1 == brecord) { + card->stop(); + emit status(i18n("idle")); + } + } +} + +void +BufferList::set_level(int l) +{ + level = l; +} + +void +BufferList::record() +{ + if (-1 != bplayback || -1 != brecord) + stop(); + if (-1 == (brecord = listbox->currentItem())) { + add_rambuffer(); + brecord = count-1; + } + if (buffers[brecord]->is_busy()) { + KMessageBox::error(NULL, i18n("buffer is busy")); + brecord = -1; + return; + } + if (-1 == card->start_record()) { + xperror(i18n("can't open soundcard")); + brecord = -1; + return; + } + buffers[brecord]->balloc(); + buffers[brecord]->start_write(¶ms); + buffers[brecord]->seek(0); + label_buffer(brecord); + if (level) { + wait = 1; + emit status(i18n("waiting...")); + } else { + wait = 0; + emit status(i18n("recording...")); + } +} + +void +BufferList::play() +{ + struct SOUNDPARAMS *p; + + if (-1 != bplayback || -1 != brecord) + stop(); + if (-1 == (bplayback = listbox->currentItem())) { + /* error message ?? */ + bplayback = -1; + return; + } + if (buffers[bplayback]->is_busy()) { + KMessageBox::error(NULL, i18n("buffer is busy")); + bplayback = -1; + return; + } + p = buffers[bplayback]->get_params(); + card->setparams(p); + if (-1 == card->start_playback()) { + xperror(i18n("can't open soundcard")); + bplayback = -1; + return; + } + buffers[bplayback]->balloc(); + buffers[bplayback]->seek(0); + + emit status(i18n("playback")); +} + +#define JUMP_SECONDS 20 + +void +BufferList::forward() +{ + struct SOUNDPARAMS *p; + int pos; + + if(-1 != bplayback) { + p = buffers[bplayback]->get_params(); + pos = buffers[bplayback]->tell(); + pos += (JUMP_SECONDS * p->rate * p->channels * + ((p->format == FMT_16BIT) ? 2 : 1)) & ~(2^14-1); + buffers[bplayback]->seek(pos); + card->kill_buffer(); + } +} + +void +BufferList::backward() +{ + struct SOUNDPARAMS *p; + int pos; + + if(-1 != bplayback) { + p = buffers[bplayback]->get_params(); + pos = buffers[bplayback]->tell(); + pos -= (JUMP_SECONDS * p->rate * p->channels * + ((p->format == FMT_16BIT) ? 2 : 1)) & ~(2^14-1); + if (pos < 0) + pos = 0; + buffers[bplayback]->seek(pos); + card->kill_buffer(); + } +} + +void +BufferList::istop() +{ + if (-1 != brecord) { + buffers[brecord]->stop_write(); + label_buffer(brecord); + buffers[brecord]->bfree(); + brecord = -1; + if (!mon) { + card->stop(); + emit status(i18n("idle")); + } else { + emit status(i18n("monitor")); + } + } + if (-1 != bplayback) { + buffers[bplayback]->bfree(); + bplayback = -1; + if (mon) { + card->start_record(); + emit status(i18n("monitor")); + } else { + card->stop(); + emit status(i18n("idle")); + } + } +} + +void +BufferList::stop() +{ + if (-1 != bplayback) { + /* stop *button*, we kill the output buffer */ + card->kill_buffer(); + } + istop(); +} + +void +BufferList::next_buffer() +{ + char filename[256]; + + if (-1 != bplayback) { + listbox->setCurrentItem(bplayback+1); + stop(); + play(); + + } else if (-1 != brecord) { + istop(); + sprintf(filename,"song%03d.wav",new_buffer_count++); + add_filebuffer(filename); + record(); + + } else { + sprintf(filename,"song%03d.wav",new_buffer_count++); + add_filebuffer(filename); + record(); + } +} 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 @@ -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(); +} @@ -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 Binary files differnew file mode 100755 index 0000000..fbac1f1 --- /dev/null +++ b/img/1rightarrow.png diff --git a/img/2leftarrow.png b/img/2leftarrow.png Binary files differnew file mode 100755 index 0000000..629c4ff --- /dev/null +++ b/img/2leftarrow.png diff --git a/img/2rightarrow.png b/img/2rightarrow.png Binary files differnew file mode 100755 index 0000000..91a01ff --- /dev/null +++ b/img/2rightarrow.png diff --git a/img/freq.png b/img/freq.png Binary files differnew file mode 100755 index 0000000..5833bb5 --- /dev/null +++ b/img/freq.png diff --git a/img/level.png b/img/level.png Binary files differnew file mode 100755 index 0000000..e7357fa --- /dev/null +++ b/img/level.png diff --git a/img/line_monitor.png b/img/line_monitor.png Binary files differnew file mode 100755 index 0000000..d1838c4 --- /dev/null +++ b/img/line_monitor.png diff --git a/img/mrecord.png b/img/mrecord.png Binary files differnew file mode 100755 index 0000000..6ea6809 --- /dev/null +++ b/img/mrecord.png diff --git a/img/player_stop.png b/img/player_stop.png Binary files differnew file mode 100755 index 0000000..5ed11cd --- /dev/null +++ b/img/player_stop.png 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 "buffer #nr". +<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<nr>.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 "<tt>cdrecord -audio +song*.wav</tt>". +</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 <Florian.Kolbe@in-gmbh.de> (input level window) +<li>Stéphane Gourichon <gouricho@poleia.lip6.fr> (input level patches) +<li>Thomas Strehl <tstrehl@suse.de> (KDE2 port) +</ul> + +<hr noshade> +<address>Gerd Knorr +<<a href="mailto:kraxel@goldbach.in-berlin.de">kraxel@goldbach.in-berlin.de</a>> +</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 */ @@ -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 */ @@ -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 + @@ -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 */ @@ -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(¤t); + + 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(¤t); + 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(¤t,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; +} @@ -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 |