From f172e7016a13f435adfbb93a85d5e22b2237a377 Mon Sep 17 00:00:00 2001 From: kraxel Date: Wed, 3 Nov 2004 12:53:56 +0000 Subject: - add files. --- GNUmakefile | 153 ++++++++++ INSTALL | 59 ++++ buffer.cpp | 841 +++++++++++++++++++++++++++++++++++++++++++++++++++ buffer.h | 192 ++++++++++++ byteorder.h | 49 +++ fft.cpp | 218 +++++++++++++ fft.h | 40 +++ img/1rightarrow.png | Bin 0 -> 431 bytes img/2leftarrow.png | Bin 0 -> 740 bytes img/2rightarrow.png | Bin 0 -> 418 bytes img/freq.png | Bin 0 -> 362 bytes img/level.png | Bin 0 -> 497 bytes img/line_monitor.png | Bin 0 -> 733 bytes img/mrecord.png | Bin 0 -> 389 bytes img/player_stop.png | Bin 0 -> 345 bytes index.html | 118 ++++++++ krecord.cpp | 658 ++++++++++++++++++++++++++++++++++++++++ krecord.h | 122 ++++++++ level.cpp | 438 +++++++++++++++++++++++++++ level.h | 56 ++++ mix.c | 75 +++++ mk/Autoconf.mk | 138 +++++++++ mk/Compile.mk | 88 ++++++ mk/Maintainer.mk | 12 + mk/Variables.mk | 46 +++ oss.cpp | 358 ++++++++++++++++++++++ oss.h | 68 +++++ sound.cpp | 272 +++++++++++++++++ sound.h | 76 +++++ soundfft.c | 196 ++++++++++++ soundfft.h | 5 + sunaudio.cpp | 369 ++++++++++++++++++++++ sunaudio.h | 68 +++++ 33 files changed, 4715 insertions(+) create mode 100644 GNUmakefile create mode 100644 INSTALL create mode 100644 buffer.cpp create mode 100644 buffer.h create mode 100644 byteorder.h create mode 100644 fft.cpp create mode 100644 fft.h create mode 100755 img/1rightarrow.png create mode 100755 img/2leftarrow.png create mode 100755 img/2rightarrow.png create mode 100755 img/freq.png create mode 100755 img/level.png create mode 100755 img/line_monitor.png create mode 100755 img/mrecord.png create mode 100755 img/player_stop.png create mode 100644 index.html create mode 100644 krecord.cpp create mode 100644 krecord.h create mode 100644 level.cpp create mode 100644 level.h create mode 100644 mix.c create mode 100644 mk/Autoconf.mk create mode 100644 mk/Compile.mk create mode 100644 mk/Maintainer.mk create mode 100644 mk/Variables.mk create mode 100644 oss.cpp create mode 100644 oss.h create mode 100644 sound.cpp create mode 100644 sound.h create mode 100644 soundfft.c create mode 100644 soundfft.h create mode 100644 sunaudio.cpp create mode 100644 sunaudio.h diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..00b2988 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,153 @@ +# configuration +-include Make.config +include mk/Variables.mk + +# QT/KDE dirs +ifeq ($(QTDIR),) +QTDIR := /usr +foobar := $(shell echo "WARNING: QTDIR isn't set, assuming /usr" >&2) +endif +ifeq ($(KDEDIR),) +KDEDIR := /usr +foobar := $(shell echo "WARNING: KDEDIR isn't set, assuming /usr" >&2) +endif +prefix := $(KDEDIR) +export QTDIR KDEDIR + +# kde install directories +appsdir := $(DESTDIR)$(shell sh kdedirs apps)/Multimedia +datadir := $(DESTDIR)$(shell sh kdedirs data)/krecord +htmldir := $(DESTDIR)$(shell sh kdedirs html)/en/krecord + +# default target +all: build + + +########################################################################## +# fixup flags depending on the environment variables (QTDIR, KDEDIR) + +# X11R6 +X11DIR := /usr/X11R6 +CFLAGS += -I$(X11DIR)/include +LDFLAGS += -L$(X11DIR)/$(LIB) + +# Qt +ifeq ($(QTDIR),/usr) +CFLAGS += -I/usr/include/qt +else +CFLAGS += -I$(QTDIR)/include +LDFLAGS += -L$(QTDIR)/$(LIB) +endif + +# KDE +ifeq ($(KDEDIR),/usr) +CFLAGS += -I/usr/include/kde +else +CFLAGS += -I$(KDEDIR)/include +LDFLAGS += -L$(KDEDIR)/$(LIB) +endif + +# version +CFLAGS += -DKRECORD_VERSION='"$(VERSION)"' +CFLAGS += -fno-strict-aliasing # fft code needs this + + +########################################################################## +# poor man's autoconf + +include mk/Autoconf.mk + +define make-config +LIB := $(LIB) +HAVE_SOUNDCARD_H := $(call ac_header,soundcard.h) +HAVE_SYS_SOUNDCARD_H := $(call ac_header,sys/soundcard.h) +HAVE_SUN_AUDIOIO_H := $(call ac_header,sun/audioio.h) +HAVE_LIBQT_MT := $(call ac_lib,qt_wm_state,qt-mt) +endef + +# config conditionals +includes := SOUNDCARD_H SYS_SOUNDCARD_H SUN_AUDIOIO_H +inc_cflags := $(call ac_inc_cflags,$(includes)) +CFLAGS += $(inc_cflags) + +ifeq ($(HAVE_LIBQT_MT),yes) +QTLIB=-lqt-mt +else +QTLIB=-lqt +endif + + +########################################################################## +# my targets + +# krecord +TARGET := krecord +OBJS := krecord.o sound.o fft.o level.o buffer.o soundfft.o \ + oss.o sunaudio.o +SRCS := krecord.cpp sound.cpp fft.cpp level.cpp buffer.cpp soundfft.c \ + oss.cpp sunaudio.cpp +MOCS := krecord.moc sound.moc fft.moc level.moc buffer.moc \ + oss.moc sunaudio.moc + +CXXFLAGS := $(CFLAGS) +LDLIBS += -lkdeui -lkdecore $(QTLIB) -lXext -lXmu -lX11 + +# locales +PO := $(wildcard po/*.po) +MO := $(subst .po,.mo,$(PO)) +MERGE := $(subst .po,.pox,$(PO)) +LANGS := $(patsubst po/%.po,install-lang-%,$(PO)) + +build: $(TARGET) $(MO) + +$(TARGET): $(OBJS) + +install: install-bin $(LANGS) + +install-bin: $(TARGET) + $(INSTALL_DIR) $(bindir) + $(INSTALL_BINARY) $(TARGET) $(bindir) + $(INSTALL_DIR) $(appsdir) + $(INSTALL_DATA) krecord.kdelnk $(appsdir) + $(INSTALL_DIR) $(datadir)/toolbar + $(INSTALL_DATA) img/*.png $(datadir)/toolbar + $(INSTALL_DIR) $(htmldir) + $(INSTALL_DATA) index.html $(htmldir) + +install-lang-%: po/%.mo + $(INSTALL_DIR) $(locdir)/$*/LC_MESSAGES + $(INSTALL_DATA) $< $(locdir)/$*/LC_MESSAGES/krecord.mo + +clean: + rm -f *.o $(depfiles) + rm -f *.bak *~ *% core* "#*" + rm -f po/*~ po/*.mo + +realclean distclean:: clean + rm -f $(TARGET) $(MOCS) Make.config + +po/krecord.pot: $(SRCS) + touch messages.po + xgettext -sj --keyword=i18n $(SRCS) + mv messages.po $@ + +locale: po/krecord.pot $(MERGE) + + +########################################################################## +# some rules + +%.pox : %.po po/krecord.pot + msgmerge $< po/krecord.pot > $@ + +buffer.o: buffer.moc +fft.o: fft.moc +krecord.o: krecord.moc +level.o: level.moc +oss.o: oss.moc +sound.o: sound.moc +sunaudio.o: sunaudio.moc + +include mk/Compile.mk +include mk/Maintainer.mk +-include $(depfiles) diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..0d1ba36 --- /dev/null +++ b/INSTALL @@ -0,0 +1,59 @@ + +howto compile and install this package +====================================== + + +really short install instructions +--------------------------------- + + $ make + $ su -c "make install" + + + +the more detailed version +------------------------- + +Make sure you use GNU make. The file name "GNUmakefile" isn't a joke, +this package really requires GNU make. + +As first step make will do some config checks on your system and write +the results to Make.config. If you want to have a look at Make.config +before the actual build starts you can run this step separately using +"make config". + +The Makefiles use the usual GNU-ish Makefile conventions for variable +names and default values, i.e. prefix=/usr/local, ... + +The values for some frequently adapted variables are initialized from +the enviroment. Thus you can change the defaults simply by setting +environment variables: + + $ prefix="/usr" + $ CFLAGS="-O3 -mcpu=i686" + $ export prefix CFLAGS + +Almost any variable can be overridden on the make command line. It is +often used this way to install into some buildroot for packaging ... + + $ su -c "make DESTDIR=/tmp/buildroot install" + +... but it works for most other variables equally well. There are +some exceptions through, it usually does _not_ work for CFLAGS for +example. + +Try "make verbose=yes" if you want to see the complete command lines +executed by make instead of the short messages (for trouble shooting, +because you like this way, for whatever reason ...). This also makes +the config checks performed by "make config" more verbose. + +If you don't trust my Makefiles you can run "make -n install" to see +what "make install" would do on your system. It will produce +human-readable output (unlike automake ...). + +Have fun, + + Gerd + +-- +Gerd Knorr 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 +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "byteorder.h" +#include "sound.h" +#include "buffer.moc" + +extern KLocale *globalKlocale; + +/* ---------------------------------------------------------------------- */ + +void +xperror(const char *msg) +{ + char text[256]; + + sprintf(text,"%s: %s", msg, strerror(errno)); + KMessageBox::error(NULL, text); +} + +/* ---------------------------------------------------------------------- */ + +AudioBuffer::AudioBuffer() +{ + size = 0; + busy = 0; + position = 0; + clippedsamples = 0; +} + +AudioBuffer::~AudioBuffer() {} + +int AudioBuffer::is_busy() { return busy; } +void AudioBuffer::balloc(void) +{ + busy++; + if (busy != 1) + fprintf(stderr,"alloc: buffer reference count bug: %d\n",busy); +} +void AudioBuffer::bfree(void) +{ + busy--; + if (busy != 0) + fprintf(stderr,"free: buffer reference count bug: %d\n",busy); +} + +int AudioBuffer::start_write(struct SOUNDPARAMS*) { return 0; } +void AudioBuffer::stop_write() {} +struct SOUNDPARAMS* AudioBuffer::get_params() { return NULL; } +int AudioBuffer::get_size() { return 0; } +char* AudioBuffer::name() { return NULL; } + +void* AudioBuffer::read_audio(int len) { return 0; } +int AudioBuffer::write_audio(int len, void *data) { return 0; } +int AudioBuffer::seek(int pos) { return 0; } +int AudioBuffer::tell() { return 0; } + +/* ---------------------------------------------------------------------- */ + +#define BUFFER_SIZE 0x10000 +#define BUFFER_MASK 0x0ffff +#define BUFFER_SHIFT 16 + +RAMBuffer::RAMBuffer() +{ + static int counter = 0; + + memset(¶ms,0,sizeof(struct SOUNDPARAMS)); + sprintf(bufname,"buffer #%d",++counter); + buffers = (char**)malloc(sizeof(void*)); + buffers[0] = (char*)malloc(BUFFER_SIZE); + bufcount = 1; + position = 0; +} + +RAMBuffer::~RAMBuffer() +{ + int i; + + for (i = 0; i < bufcount; i++) { + free(buffers[i]); + } + free(buffers); +} + +int +RAMBuffer::start_write(struct SOUNDPARAMS *p) +{ + memcpy(¶ms,p,sizeof(struct SOUNDPARAMS)); + return 0; +} + +void RAMBuffer::stop_write() {}; + +struct SOUNDPARAMS* +RAMBuffer::get_params() +{ + return ¶ms; +} + +int +RAMBuffer::get_size() +{ + return size; +} + +char* +RAMBuffer::name() +{ + return bufname; +} + +void* +RAMBuffer::read_audio(int len) +{ + /* FIXME (?): one can't read over buffer boundaries */ + char *ptr; + + if (position+len > size) + return NULL; + ptr = buffers[position>>BUFFER_SHIFT]; + ptr += (position&BUFFER_MASK); + position += len; + return ptr; +} + +int +RAMBuffer::write_audio(int len, void *data) +{ + /* dito for write - not over buffer boundaries */ + if ((position>>BUFFER_SHIFT) >= bufcount) { + buffers = (char**)realloc(buffers,sizeof(void*)*(bufcount+1)); + if (NULL == (buffers[bufcount] = (char*)malloc(BUFFER_SIZE))) + return -1; + bufcount++; + } + memcpy(buffers[position>>BUFFER_SHIFT]+(position&BUFFER_MASK), data, len); + + position += len; + if (size < position) + size = position; + return 0; +} + +int +RAMBuffer::seek(int pos) +{ + if (pos > size) + return -1; + position = pos; + return position; +} + +int +RAMBuffer::tell() +{ + return position; +} + +/* ---------------------------------------------------------------------- */ + +FileBuffer::FileBuffer() +{ + memset(¶ms,0,sizeof(struct SOUNDPARAMS)); + fd = -1; + size = position = 0; + bstart = bstop = 0; +} + +FileBuffer::~FileBuffer() +{ + if (fd == -1) + return; + close(fd); +} + +void +FileBuffer::init_header() +{ + /* stolen from cdda2wav */ + int nBitsPerSample = 8; + if (params.format == FMT_16BIT) + nBitsPerSample = 16; + + unsigned long nBlockAlign = params.channels * ((nBitsPerSample + 7) / 8); + unsigned long nAvgBytesPerSec = nBlockAlign * params.rate; + unsigned long temp = /* data length */ 0 + + sizeof(WAVEHDR) - sizeof(CHUNKHDR); + + fileheader.chkRiff.ckid = cpu_to_le32(FOURCC_RIFF); + fileheader.fccWave = cpu_to_le32(FOURCC_WAVE); + fileheader.chkFmt.ckid = cpu_to_le32(FOURCC_FMT); + fileheader.chkFmt.dwSize = cpu_to_le32(16); + fileheader.wFormatTag = cpu_to_le16(WAVE_FORMAT_PCM); + fileheader.nChannels = cpu_to_le16(params.channels); + fileheader.nSamplesPerSec = cpu_to_le32(params.rate); + fileheader.nAvgBytesPerSec = cpu_to_le32(nAvgBytesPerSec); + fileheader.nBlockAlign = cpu_to_le16(nBlockAlign); + fileheader.wBitsPerSample = cpu_to_le16(nBitsPerSample); + fileheader.chkData.ckid = cpu_to_le32(FOURCC_DATA); + fileheader.chkRiff.dwSize = cpu_to_le32(temp); + fileheader.chkData.dwSize = cpu_to_le32(0 /* data length */); +} + +int +FileBuffer::attach(const char *file) +{ + int new_file = 0; + + if (-1 != fd) + close(fd); + + ro = 0; + position = 0; + size = 0; + offset = sizeof(WAVEHDR); + strcpy(filename,file); + + if (-1 == (fd = open(filename,O_RDWR))) { + if (errno == ENOENT) { + if (-1 == (fd = open(filename,O_RDWR|O_CREAT,0666))) { + xperror(i18n("can't create wav file")); + return -1; + } + new_file = 1; + } else { + if (-1 == (fd = open(filename,O_RDONLY))) { + xperror(i18n("can't open wav file")); + return -1; + } else + ro = 1; + } + } + fcntl(fd,F_SETFD,FD_CLOEXEC); + if (!new_file) { + read(fd,&fileheader,offset); + if (!IS_STD_WAV_HEADER(fileheader)) { +#if 0 /* nice for debugging, but annonying for everyday usage */ + KMessageBox::error(NULL, i18n("not a wav file")); +#endif + return -1; + } + if (le16_to_cpu(fileheader.wFormatTag) != WAVE_FORMAT_PCM) { + KMessageBox::error(NULL,i18n("unsupported audio format")); + return -1; + } + params.format = FMT_8BIT; + if (16 == le16_to_cpu(fileheader.wBitsPerSample)) + params.format = FMT_16BIT; + params.channels = le16_to_cpu(fileheader.nChannels); + params.rate = cpu_to_le32(fileheader.nSamplesPerSec); + size = le32_to_cpu(fileheader.chkData.dwSize); + } + return 0; +} + +int +FileBuffer::start_write(struct SOUNDPARAMS *p) +{ + memcpy(¶ms,p,sizeof(struct SOUNDPARAMS)); + init_header(); + lseek(fd,0,SEEK_SET); + write(fd,&fileheader,offset); + return 0; +} + +void +FileBuffer::stop_write() +{ + unsigned long temp = size + sizeof(WAVEHDR) - sizeof(CHUNKHDR); + + fileheader.chkRiff.dwSize = cpu_to_le32(temp); + fileheader.chkData.dwSize = cpu_to_le32(size); + lseek(fd,0,SEEK_SET); + write(fd,&fileheader,offset); +} + +struct SOUNDPARAMS* +FileBuffer::get_params() +{ + return ¶ms; +} + +int +FileBuffer::get_size() +{ + return size; +} + +char* +FileBuffer::name() +{ + return filename; +} + +void* +FileBuffer::read_audio(int len) +{ + int rc; + + if (position+len > size) + return NULL; + + /* printf("[%d - %d (+%d) - %d] / %d\n",bstart,position,len,bstop,size); */ + if (position < bstart || position+len > bstop) { + rc = read(fd,buffer,65536); +#if BYTE_ORDER == BIG_ENDIAN + if (params.format == FMT_16BIT) { + /* byteswap 16bit pcm on bigendian machines */ + int i; + char h; + for (i = 0; i < rc; i += 2) { + h = buffer[i]; + buffer[i] = buffer[i+1]; + buffer[i+1] = h; + } + } +#endif + if (-1 == rc) + return NULL; + bstart = position, bstop = position+rc; + } + position += len; + return buffer+position-bstart-len; +} + +int +FileBuffer::write_audio(int len, void *data) +{ + int rc; + +#if BYTE_ORDER == BIG_ENDIAN + char *buf; + int i; + + if (params.format == FMT_16BIT) { + /* byteswap 16bit pcm on bigendian machines */ + buf = (char*)malloc(len); + for (i = 0; i < len; i += 2) { + buf[i] = ((char*)data)[i+1]; + buf[i+1] = ((char*)data)[i]; + } + rc = write(fd,buf,len); + free(buf); + } else { + rc = write(fd,data,len); + } +#else + rc = write(fd,data,len); +#endif + if (len == rc) { + position += len; + if (position > size) + size = position; + return 0; + } else + return -1; +} + +int +FileBuffer::seek(int pos) +{ + if (pos > size) + return -1; + position = pos; + if (-1 == lseek(fd,position+offset,SEEK_SET)) + perror("fb: lseek"); + return 0; +} + +int +FileBuffer::tell() +{ + return position; +} + +/* ---------------------------------------------------------------------- */ + +BufferList::BufferList(QListBox *l, Soundcard *c) +{ + listbox = l; + card = c; + count = 0; + brecord = -1; + bplayback = -1; + mon = 0; + level = 0; + wait = 0; + new_buffer_count = 0; + + connect(card,SIGNAL(newparams(struct SOUNDPARAMS*)), + this, SLOT(new_params(struct SOUNDPARAMS*))); + connect(card,SIGNAL(senddata(void*)), + this, SLOT(new_data(void*))); + connect(card,SIGNAL(receivedata(void*)), + this, SLOT(post_data(void*))); +} + +int +BufferList::add_filebuffer(const char *filename) +{ + FileBuffer *fbuffer; + + fbuffer = new FileBuffer(); + if (-1 == fbuffer->attach(filename)) { + delete fbuffer; + return -1; + } + add_buffer(fbuffer); + return 0; +} + +int +BufferList::add_rambuffer() +{ + add_buffer(new RAMBuffer()); + return 0; +} + +void +BufferList::add_buffer(AudioBuffer *buf) +{ + if (count) + buffers = (AudioBuffer**)realloc + (buffers,sizeof(AudioBuffer*)*(count+1)); + else + buffers = (AudioBuffer**)malloc(sizeof(AudioBuffer*)); + buffers[count] = buf; + listbox->insertItem("*",count); + listbox->setCurrentItem(count); + label_buffer(count); + count++; +} + +void +BufferList::label_buffer(int buf) +{ + struct SOUNDPARAMS *p; + int sec,len,size; + char text[256]; + + p = buffers[buf]->get_params(); + if (p->format != FMT_UNDEFINED) { + size = buffers[buf]->get_size(); + sec = size/p->rate/p->channels; + if (p->format == FMT_16BIT) + sec /= 2; + len = sprintf(text,"%5d %s %s %d:%02d / ", + p->rate, + (p->channels == 1) + ? (const char*) i18n("mono") + : (const char*) i18n("stereo"), + sndfmt2str(p->format), + sec/60,sec%60); + if (size>>20) + len += sprintf(text+len,"%d.%dMB ", + size>>20,((size&0xfffff)*10)>>20); + else + len += sprintf(text+len,"%dkB ",size>>10); + } else { + strcpy(text,i18n("new")); + strcat(text," "); + } + strcat(text,buffers[buf]->name()); + if (p->format != FMT_UNDEFINED && buffers[buf]->clippedsamples) { + sprintf(text+strlen(text)," (%2d samples clipped)", + buffers[buf]->clippedsamples); + } + listbox->changeItem(text,buf); +} + +void +BufferList::del_buffer(int buf) +{ + int i; + + if (buffers[buf]->is_busy()) { + KMessageBox::error(NULL, i18n("buffer is busy")); + return; + } + listbox->removeItem(buf); + delete buffers[buf]; + for (i = buf+1; i < count; i++) + buffers[i-1] = buffers[i]; + count--; + if (0 == count) + free(buffers); +} + +void +BufferList::new_params(struct SOUNDPARAMS *p) +{ + memcpy(¶ms,p,sizeof(struct SOUNDPARAMS)); +} + +void +BufferList::new_data(void *data) +{ + static int count; + int i,j,max; + unsigned char *c; + short *s; + + int clippedhere=0; + + if (wait) { + if (params.format == FMT_16BIT) { + for (i = (params.blocksize>>1)-1, s=(short*)data, max = 0; i; i--) + if (abs(s[i]) > max) + max = abs(s[i]); + max = max * 100 / 32768; + } else { + for (i = params.blocksize-1, c=(unsigned char*)data, max = 0; i; i--) + if ((j = abs((int)c[i]-128)) > max) + max = j; + max = max * 100 / 128; + } + if (max >= level) { + wait = 0; + emit status(i18n("record")); + } else + return; + } + + /* we're here if we're not waiting */ + /* we have to check for possible clipping */ + if (params.format == FMT_16BIT) { + for (i = (params.blocksize>>1)-1, s=(short*)data, max = 0; i; i--) + if ((s[i]==32767) || (s[i]==-32768)) { + //fprintf(stderr,"!"); + clippedhere++; + } + max = max * 100 / 32768; + } else { + for (i = params.blocksize-1, c=(unsigned char*)data, max = 0; i; i--) { + j = abs((int)c[i]-128); + if ((j==127) || (j==-128)) { + //fprintf(stderr,"!"); + clippedhere++; + } + } + max = max * 100 / 128; + } + + //fprintf(stderr,"%d\n",clippedhere); + + if (-1 != brecord) { + buffers[brecord]->clippedsamples += clippedhere; + if (-1 == buffers[brecord]->write_audio(params.blocksize,data)) { + istop(); + xperror(i18n("can't save sound data")); + } + if (++count == 16) { + count = 0; + label_buffer(brecord); + set_pos_size(i18n("record"),brecord); + } + } +} + +void +BufferList::post_data(void *data) +{ + static int count = 0; + void *ptr; + + if (NULL == (ptr = buffers[bplayback]->read_audio(params.blocksize))) { + memset(data,0,params.blocksize); + istop(); + //fprintf(stderr,"->EOF"); + return; + } + memcpy(data,ptr,params.blocksize); + + if (++count == 16) { + count = 0; + set_pos_size(i18n("playback"),bplayback); + } +} + +void +BufferList::set_pos_size(const char *start, int buf) +{ + struct SOUNDPARAMS *p; + int sec,len,size; + char text[256]; + + p = buffers[buf]->get_params(); + + size = buffers[buf]->tell(); + sec = size/p->rate/p->channels; + if (p->format == FMT_16BIT) + sec /= 2; + len = sprintf(text,"%s %d:%02d / ",start,sec/60,sec%60); + + size = buffers[buf]->get_size(); + sec = size/p->rate/p->channels; + if (p->format == FMT_16BIT) + sec /= 2; + sprintf(text+len,"%d:%02d",sec/60,sec%60); + emit status(text); +} + +/* ---------------------------------------------------------------------- */ + +void +BufferList::new_ram() +{ + add_rambuffer(); +} + +void +BufferList::save_buf(const char *filename) +{ + int i; + void *data; + struct SOUNDPARAMS *p; + FileBuffer fbuf; + + if (-1 == (i = listbox->currentItem())) + return; + if (buffers[i]->is_busy()) { + KMessageBox::error(NULL, i18n("buffer is busy")); + return; + } + p = buffers[i]->get_params(); + buffers[i]->seek(0); + + if (-1 == fbuf.attach(filename)) + return; + fbuf.start_write(p); + while (NULL != (data = buffers[i]->read_audio(p->blocksize))) { + if (-1 == fbuf.write_audio(p->blocksize,data)) { + xperror(i18n("can't write sound data")); + break; + } + } + fbuf.stop_write(); +} + +void +BufferList::del_buf() +{ + int i; + + if (-1 != (i = listbox->currentItem())) + del_buffer(i); +} + +void +BufferList::monitor() +{ + mon = !mon; + if (mon) { + if (-1 == card->start_record()) { + xperror(i18n("can't open soundcard")); + return; + } + emit status(i18n("monitor")); + } else { + if (-1 == brecord) { + card->stop(); + emit status(i18n("idle")); + } + } +} + +void +BufferList::set_level(int l) +{ + level = l; +} + +void +BufferList::record() +{ + if (-1 != bplayback || -1 != brecord) + stop(); + if (-1 == (brecord = listbox->currentItem())) { + add_rambuffer(); + brecord = count-1; + } + if (buffers[brecord]->is_busy()) { + KMessageBox::error(NULL, i18n("buffer is busy")); + brecord = -1; + return; + } + if (-1 == card->start_record()) { + xperror(i18n("can't open soundcard")); + brecord = -1; + return; + } + buffers[brecord]->balloc(); + buffers[brecord]->start_write(¶ms); + buffers[brecord]->seek(0); + label_buffer(brecord); + if (level) { + wait = 1; + emit status(i18n("waiting...")); + } else { + wait = 0; + emit status(i18n("recording...")); + } +} + +void +BufferList::play() +{ + struct SOUNDPARAMS *p; + + if (-1 != bplayback || -1 != brecord) + stop(); + if (-1 == (bplayback = listbox->currentItem())) { + /* error message ?? */ + bplayback = -1; + return; + } + if (buffers[bplayback]->is_busy()) { + KMessageBox::error(NULL, i18n("buffer is busy")); + bplayback = -1; + return; + } + p = buffers[bplayback]->get_params(); + card->setparams(p); + if (-1 == card->start_playback()) { + xperror(i18n("can't open soundcard")); + bplayback = -1; + return; + } + buffers[bplayback]->balloc(); + buffers[bplayback]->seek(0); + + emit status(i18n("playback")); +} + +#define JUMP_SECONDS 20 + +void +BufferList::forward() +{ + struct SOUNDPARAMS *p; + int pos; + + if(-1 != bplayback) { + p = buffers[bplayback]->get_params(); + pos = buffers[bplayback]->tell(); + pos += (JUMP_SECONDS * p->rate * p->channels * + ((p->format == FMT_16BIT) ? 2 : 1)) & ~(2^14-1); + buffers[bplayback]->seek(pos); + card->kill_buffer(); + } +} + +void +BufferList::backward() +{ + struct SOUNDPARAMS *p; + int pos; + + if(-1 != bplayback) { + p = buffers[bplayback]->get_params(); + pos = buffers[bplayback]->tell(); + pos -= (JUMP_SECONDS * p->rate * p->channels * + ((p->format == FMT_16BIT) ? 2 : 1)) & ~(2^14-1); + if (pos < 0) + pos = 0; + buffers[bplayback]->seek(pos); + card->kill_buffer(); + } +} + +void +BufferList::istop() +{ + if (-1 != brecord) { + buffers[brecord]->stop_write(); + label_buffer(brecord); + buffers[brecord]->bfree(); + brecord = -1; + if (!mon) { + card->stop(); + emit status(i18n("idle")); + } else { + emit status(i18n("monitor")); + } + } + if (-1 != bplayback) { + buffers[bplayback]->bfree(); + bplayback = -1; + if (mon) { + card->start_record(); + emit status(i18n("monitor")); + } else { + card->stop(); + emit status(i18n("idle")); + } + } +} + +void +BufferList::stop() +{ + if (-1 != bplayback) { + /* stop *button*, we kill the output buffer */ + card->kill_buffer(); + } + istop(); +} + +void +BufferList::next_buffer() +{ + char filename[256]; + + if (-1 != bplayback) { + listbox->setCurrentItem(bplayback+1); + stop(); + play(); + + } else if (-1 != brecord) { + istop(); + sprintf(filename,"song%03d.wav",new_buffer_count++); + add_filebuffer(filename); + record(); + + } else { + sprintf(filename,"song%03d.wav",new_buffer_count++); + add_filebuffer(filename); + record(); + } +} 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 +#include +#include + +#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 + +#if defined(_LITTLE_ENDIAN) +#define BYTE_ORDER LITTLE_ENDIAN +#elif defined(_BIG_ENDIAN) +#define BYTE_ORDER BIG_ENDIAN +#endif + +#else + +/* linux */ +#include + +#endif + + +/* ---------------------------------------------------------------------- */ + +#if BYTE_ORDER == LITTLE_ENDIAN + +# define cpu_to_le32(x) (x) +# define cpu_to_le16(x) (x) +# define le32_to_cpu(x) (x) +# define le16_to_cpu(x) (x) + +#elif BYTE_ORDER == BIG_ENDIAN + +# define cpu_to_le32(x) (\ + ((x & 0x000000FF) << 24) | \ + ((x & 0x0000FF00) << 8) | \ + ((x & 0x00FF0000) >> 8) | \ + ((x & 0xFF000000) >> 24)) +# define le32_to_cpu(x) cpu_to_le32(x) + +# define cpu_to_le16(x) (((x & 0x00FF) << 8) | (x >> 8)) +# define le16_to_cpu(x) cpu_to_le16(x) + +#else + +# error Unknow byte order type + +#endif diff --git a/fft.cpp b/fft.cpp new file mode 100644 index 0000000..525a584 --- /dev/null +++ b/fft.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include + +#include + +#include "sound.h" +#include "fft.moc" + +#include + +extern "C" { + #include "soundfft.h" +} + +#define FFT_MAX 8192 +#define FFT_LEFT 100 +#define FFT_RIGHT 10000 +#define DB_MAX 0.0 +#define DB_MIN -100.0 + +/* ---------------------------------------------------------------------- */ + +FFTWindow::FFTWindow(QWidget *parent, char *name) : QWidget(parent,name,0) +{ + XColor color,dummy; + Colormap map = DefaultColormapOfScreen + (DefaultScreenOfDisplay(x11Display())); + + gc = XCreateGC(x11Display(),winId(),0,NULL); + XAllocNamedColor(x11Display(),map,"white",&color,&dummy); + back = color.pixel; + XAllocNamedColor(x11Display(),map,"red",&color,&dummy); + fore = color.pixel; + + /* flag: no init so far... */ + channels = 0; + logmap = NULL; + ylog = false; +} + +void +FFTWindow::make_logmap() +{ + int i,w,h; + float freq,up; + + w = width(); + h = height(); + + if (!w || !h || !channels) + return; + + if (logmap) { + free(logmap); + free(segments); + free(buffer); + EndFFT(); + } + logmap = (int*) malloc((w+1)*sizeof(int)); + segments = (XSegment*)malloc((w)*sizeof(XSegment)); + buffer = (short*)malloc(fft_size*sizeof(short)*channels); + InitializeFFT(fft_size); + + up = log(FFT_RIGHT/FFT_LEFT)/log(2); + for (i = 0; i <= w; i++) { + freq = pow(2,up*i/w)* FFT_LEFT; + logmap[i] = (int)(fft_size*freq/rate); + } + for (i = 0; i < w; i++) { + segments[i].x1 = i; + segments[i].x2 = i; + segments[i].y2 = h; + } +} + +void +FFTWindow::calculate(unsigned char *data) +{ + int i,j; + + if (NULL == logmap) { + fprintf(stderr,"Oops: fft no logmap (yet)\n"); + return; + } + + switch (afmt) { + case FMT_8BIT: + for (i = 0; i < audio_size; i++) + buffer[i] = (data[i] ^ 0x80) << 8; + break; +#if 0 + case AFMT_S8: + for (i = 0; i < audio_size; i++) + buffer[i] = data[i] << 8; + break; +#endif + case FMT_16BIT: + memcpy(buffer,data,audio_size); +#if 0 + /* byte swap */ + for (i = 0; i < fft_size; i++) + buffer[i] = + ((buffer[i] & 0xff00) >> 8) | + ((buffer[i] & 0x00ff) << 8); + /* unsigned -> signed */ + if (afmt == AFMT_U16_LE || afmt == AFMT_U16_BE) + for (i = 0; i < fft_size; i++) + buffer[i] ^= 0x8000; +#endif + break; + default: + fprintf(stderr,"oops(fft): unknown sound format\n"); + exit(1); + } + + for (i = 0, lmax = 0; i < fft_size; i++) + if ((j = abs((signed short)buffer[i])) > lmax) + lmax = j; + + if (channels == 2) { + for (i = 0, j = 0; i < fft_size; i++, j+=2) + buffer[i] = (buffer[j]+buffer[j+1])>>1; + } + RealFFT(buffer); +} + +void +FFTWindow::drawhist() +{ + XGCValues gcval; + + int re,im,ab; + int i,j,max,w,h; + int len = fft_size>>1; + + w = width(); + h = height(); + for (i = 0; i < w; i++) { + max = 0; + j = logmap[i]; + do { + if (j >= len) + break; + re = buffer[BitReversed[j]]; + im = buffer[BitReversed[j]+1]; + ab = re*re+im*im; + if (ab > max) + max = ab; + + j++; + } while (j < logmap[i+1]); + if (ylog) { + float dBVal = 20 * log10( sqrt(max)/FFT_MAX ); + max = (int)( h * ( dBVal-DB_MIN )/( DB_MAX-DB_MIN )); + } else { + max = (int)(sqrt(max) * h / FFT_MAX); + } + segments[i].y1 = h-max; + } + + gcval.foreground = back; + XChangeGC(x11Display(),gc,GCForeground,&gcval); + XFillRectangle(x11Display(),winId(),gc,0,0,width(),height()); + + gcval.foreground = fore; + XChangeGC(x11Display(),gc,GCForeground,&gcval); + XDrawSegments(x11Display(),winId(),gc,segments,w); + if (ylog) { + float dBVal = 20 * log10( (double)lmax/32768 ); + max = h - (int)( h * ( dBVal-DB_MIN )/( DB_MAX-DB_MIN )); + } else { + max = height() - lmax*height()/32768; + } + XDrawLine(x11Display(),winId(),gc,0,max,width(),max); +} + +/* ---------------------------------------------------------------------- */ + +void +FFTWindow::resizeEvent(QResizeEvent *event) +{ + make_logmap(); +} + +void +FFTWindow::new_params(struct SOUNDPARAMS *p) +{ + afmt = p->format; + channels = p->channels; + rate = p->rate; + audio_size = p->blocksize; + + fft_size = audio_size/channels/2; + if (afmt != FMT_16BIT) + fft_size *= 2; + + make_logmap(); +} + +void +FFTWindow::new_data(void *data) +{ + if (!channels || !isVisible()) + return; + + calculate((unsigned char*)data); + drawhist(); +} + +void +FFTWindow::set_ylog(int linear) +{ + ylog = (linear==0); + drawhist(); +} diff --git a/fft.h b/fft.h new file mode 100644 index 0000000..d81341f --- /dev/null +++ b/fft.h @@ -0,0 +1,40 @@ +#ifndef FFT_H +#define FFT_H + +#include +#include + +/* ------------------------------------------------------------------------ */ + +class FFTWindow : public QWidget +{ + Q_OBJECT; + +public: + FFTWindow(QWidget *parent, char *name); + +public slots: + void new_params(struct SOUNDPARAMS *params); + void new_data(void *data); + void set_ylog(int linear); + +protected: + void resizeEvent(QResizeEvent *event); + +private: + unsigned long fore,back; + GC gc; + int *logmap; + XSegment *segments; + short *buffer; + + int afmt,channels,rate; + int audio_size, fft_size, lmax; + bool ylog; + + void make_logmap(); + void calculate(unsigned char *data); + void drawhist(); +}; + +#endif diff --git a/img/1rightarrow.png b/img/1rightarrow.png new file mode 100755 index 0000000..fbac1f1 Binary files /dev/null and b/img/1rightarrow.png differ diff --git a/img/2leftarrow.png b/img/2leftarrow.png new file mode 100755 index 0000000..629c4ff Binary files /dev/null and b/img/2leftarrow.png differ diff --git a/img/2rightarrow.png b/img/2rightarrow.png new file mode 100755 index 0000000..91a01ff Binary files /dev/null and b/img/2rightarrow.png differ diff --git a/img/freq.png b/img/freq.png new file mode 100755 index 0000000..5833bb5 Binary files /dev/null and b/img/freq.png differ diff --git a/img/level.png b/img/level.png new file mode 100755 index 0000000..e7357fa Binary files /dev/null and b/img/level.png differ diff --git a/img/line_monitor.png b/img/line_monitor.png new file mode 100755 index 0000000..d1838c4 Binary files /dev/null and b/img/line_monitor.png differ diff --git a/img/mrecord.png b/img/mrecord.png new file mode 100755 index 0000000..6ea6809 Binary files /dev/null and b/img/mrecord.png differ diff --git a/img/player_stop.png b/img/player_stop.png new file mode 100755 index 0000000..5ed11cd Binary files /dev/null and b/img/player_stop.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..5e9b03b --- /dev/null +++ b/index.html @@ -0,0 +1,118 @@ + + +krecord help + + + +

Help for krecord

+ +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... +

+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. +

+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. + +

Description

+ +mostly TODO, currently only the most important stuff is listed. +

+The main part of the window is just a list of the buffers you have. +Empty after startup. krecord knows two sorts of buffers: +

+
memory buffers +
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". +
file buffers +
these are attached to a file and krecord reads and writes directly +from/to the file. These are listed with the filename. +
+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. +

+recording/playback starts allways at the beginning of the +buffer, there is no position scale. +

+The status line holds (from left to right): status / sample rate / +channels / audio format / latency. status might be: +

+
idle +
playback +
recording +
I think these are clear... +
monitor +
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... +
waiting +
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. +
+

+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. +

+There are a few handy keyboard shortcuts: +

+
up/down +
walk in the buffer list +
Return +
start playback +
'R' +
start recording +
Escape +
stop record/playback +
'N', Space +
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 "cdrecord -audio +song*.wav". +
+ +

Known Problems

+ +
    +
  • I have trouble with recording overruns sometimes :-( +
  • 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. +
  • Supports OSS only. Yes, there already is a sunaudio.cpp file, but +this is currently a dummy only...
    +support for big-endian machines needs some work too. +
+ +

TODO List

+ +
    +
  • more i18n +
  • portability +
+ +

Credits

+ +
    +
  • Florian Kolbe <Florian.Kolbe@in-gmbh.de> (input level window) +
  • Stéphane Gourichon <gouricho@poleia.lip6.fr> (input level patches) +
  • Thomas Strehl <tstrehl@suse.de> (KDE2 port) +
+ +
+
Gerd Knorr +<kraxel@goldbach.in-berlin.de> +
+ + 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 +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sound.h" +#include "fft.h" +#include "level.h" +#include "buffer.h" +#include "krecord.moc" + +#include +#include +#include /* 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 ", "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 \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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "sound.h" +#include "level.moc" + +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/* ---------------------------------------------------------------------- */ + +LevelWindow::LevelWindow(QWidget *parent, char *name):QWidget(parent,name,0) +{ + /* which type of vu-meter ? */ + PowervsMax=1; + LogvsLinear=1; + + /* + did not get sound-params yet. + */ + init = FALSE; + sdata = NULL; + + /* + no peaks initially. + */ + peak[0] = 0; + peak[1] = 0; + + clipLeft=false; + clipRight=false; + + /* + will use output buffer, so no background necessary. + */ + setBackgroundMode(NoBackground); + orange = QColor("orangered"); + + /* + Initialize output buffer. + */ + buffer = new QPixmap(size()); + + /* + Initialize timers to reset peaks. + */ + timer[0] = new QTimer(); + timer[1] = new QTimer(); + connect(timer[0], SIGNAL(timeout()), this, SLOT(resetPeakLeft())); + connect(timer[1], SIGNAL(timeout()), this, SLOT(resetPeakRight())); + +} /* LevelWindow */ + +LevelWindow::~LevelWindow(void) +{ + delete buffer; + delete timer[0]; + delete timer[1]; +} + +void LevelWindow::resizeEvent(QResizeEvent*) +{ + /* + Fix size of output buffer. + */ + delete buffer; + buffer = new QPixmap(size()); +} + +void LevelWindow::resetPeakLeft(void) +{ + peak[0] = 0; + clipLeft = false; + repaint(); +} + +void LevelWindow::resetPeakRight(void) +{ + peak[1] = 0; + clipRight = false; + repaint(); +} + +void LevelWindow::drawBar(QPainter& painter, int channel, float level, + int size, bool drawRed) +{ + int xLevel = (int)(((float)width())*level); /* x-pos of current level */ + int x80 = width()*80/100; /* x-pos of 80% level */ + int y,x; + + /* + Left/mono top, right bottom. + */ + if (channel == 0) { + y = 0; + } else { + y = height()/2; + } + + const QColor *colortodraw=&darkGreen; + if (drawRed) { + colortodraw=&red; + } + + /* + Green: 0%-[level|80%] + */ + painter.fillRect(0, y+1, max(1, min(xLevel, x80)), size-2, *colortodraw); + + /* + Yellow part. + */ + if (!drawRed) { + colortodraw=&darkYellow; + } + + if (level > 0.8) { + painter.fillRect(x80, y+1, max(1, xLevel-x80), size-2, *colortodraw); + } + + /* + Current peak is either reached again or pushed. + */ + if (level >= peak[channel]) { + peak[channel] = level; + timer[channel]->start(1000, TRUE); + } + + /* + Draw peak if greater than current level. + */ + if (peak[channel] >= level) { + + /* + 0- 80: green + 80- 98: yellow + 99-100: orange + */ + painter.setPen(green); + if (peak[channel] > 0.80) { + painter.setPen(yellow); + } + if (peak[channel] >= 0.99) { + painter.setPen(orange); + } + x = (int)min(width()*peak[channel]-1,width()-1); + painter.drawLine(x, y+1, x, y+size-2); + } +} /* drawBar */ + +void LevelWindow::paintEvent(QPaintEvent*) +{ + int maxLeft = 0; + int maxRight = 0; + int i; + QPainter painter; + int maxAmp; + int64_t powerLeft=0; + int64_t powerRight=0; + float floatPowerLeft=0; + float floatPowerRight=0; + char buf[32]; + +#ifndef NO_COMPENSATE_BIAS + int64_t bLeft=0; + int64_t bRight=0; +#endif + + + if ((init == FALSE) || !sdata) return; + + /* if true then calculate Power else calculate Max */ + if (PowervsMax) { + /* + Calculate power of the signal depending on format. + + Since the signal may not have an average value of 0 precisely, + we shouldn't simply calculate: + + sum_for_all_samples (pulse_value²) / number_of_samples + + but this formula assumes that the average is zero, which is not + always true (for example, in 8 bits on a Sound Blaster 64, + there is always a shift by one unit. + + We could calculate in two passes, first the average, then the + power of the measure minus the average. But we can do this in + one pass. + + Let measure = signal + bias, + where measure is the pulse value, + signal is what we want, + bias is a constant, such that the average of signal is zero. + + What we want is the value of: power = sum_for_all_samples (signal²) + + Let's calculate in the same pass: + a=sum_for_all_samples (measure²) + and + b=sum_for_all_samples (measure) + + Then a and b are equivalent to: + a = sum_for_all_samples (measure²) + = sum_for_all_samples ((signal + bias)²) + = sum_for_all_samples (signal² + bias²) + = sum_for_all_samples (signal²) + number_of_samples * bias² + + and + b = sum_for_all_samples (measure) + = bias * number_of_samples + that is, number_of_samples * bias² = b² / number_of_samples + + So a = power + b² / number_of_samples + + And power = a - b² / number_of_samples + + So we've got the correct power of the signal in one pass. + + */ + +#ifndef NO_COMPENSATE_BIAS + bLeft=0; + bRight=0; +#endif + + if (afmt == FMT_16BIT) { + + maxAmp = 32768; + if (channels == 1) { + for (i = 0; i < samples; i++) { + /* Since we calculate the square of something that can be + as big as +-32767 we assume a width of at least 32 bits + for a signed int. Moreover, we add a thousand of these + to calculate power, so 32 bits aren't enough. I chose 64 + bits unsigned int for precision. We could have switched + to float or double instead... */ + signed int thispulse=(sdata[i]); + /* Note: we calculate max value anyway, to detect clipping */ + if (abs(thispulse) > maxLeft) maxLeft = abs(sdata[i]); + powerLeft+=(thispulse*thispulse); +#ifndef NO_COMPENSATE_BIAS + bLeft+=thispulse; +#endif + } + } else + if (channels == 2) { + for (i = 0; i < samples; i++) { + signed int thispulse=(sdata[i*2]); + if (abs(thispulse) > maxLeft) maxLeft = abs(thispulse); + powerLeft+=(thispulse*thispulse); + thispulse=(sdata[i*2+1]); + if (abs(thispulse) > maxRight) maxRight = abs(thispulse); + powerRight+=(thispulse*thispulse); +#ifndef NO_COMPENSATE_BIAS + bRight+=thispulse; +#endif + } + } + } else { + unsigned char* bdata = (unsigned char*)sdata; + + maxAmp = 128; + + if (channels == 1) { + for (i = 0; i < samples; i++) { + signed int thispulse=(bdata[i]-128); + if (abs(thispulse) > maxLeft) maxLeft = abs(thispulse); + powerLeft+=(thispulse*thispulse); +#ifndef NO_COMPENSATE_BIAS + bLeft+=thispulse; +#endif + } + } else + if (channels == 2) { + for (i = 0; i < samples; i++) { + signed int thispulse=(bdata[i*2]-128); + if (abs(thispulse) > maxLeft) maxLeft = abs(thispulse); + powerLeft+=(thispulse*thispulse); + thispulse=(bdata[i*2+1]-128); + if (abs(thispulse) > maxRight) maxRight = abs(thispulse); + powerRight+=(thispulse*thispulse); +#ifndef NO_COMPENSATE_BIAS + bRight+=thispulse; +#endif + } + } + } + /* Ok for raw power. Now normalize it. */ + +#ifndef NO_COMPENSATE_BIAS + powerLeft-=bLeft*bLeft/samples; + powerRight-=bRight*bRight/samples; + //fprintf(stderr, "bLeft: %lld\tbiais: %f\t", bLeft, ((float)bLeft*(float)maxAmp/(float)samples)); +#endif + + floatPowerLeft=((float)powerLeft)/((float)maxAmp)/((float)maxAmp)/((float)samples); + floatPowerRight=((float)powerLeft)/((float)maxAmp)/((float)maxAmp)/((float)samples); + + //fprintf(stderr, "brute: %lld,\tnormalisée: %f\t", powerLeft, floatPowerLeft); + } else { + /* + Find max amplitude depending on format. + */ + if (afmt == FMT_16BIT) { + + maxAmp = 32768; + if (channels == 1) { + for (i = 0; i < samples; i++) + if (abs(sdata[i]) > maxLeft) maxLeft = abs(sdata[i]); + } else + if (channels == 2) { + for (i = 0; i < samples; i++) { + if (abs(sdata[i*2]) > maxLeft) maxLeft = abs(sdata[i*2]); + if (abs(sdata[i*2+1]) > maxRight) maxRight = abs(sdata[i*2+1]); + } + } + } else { + unsigned char* bdata = (unsigned char*)sdata; + + maxAmp = 128; + + if (channels == 1) { + for (i = 0; i < samples; i++) + if (abs(bdata[i]-128) > maxLeft) maxLeft = abs(bdata[i]-128); + } else + if (channels == 2) { + for (i = 0; i < samples; i++) { + if (abs(bdata[i*2]-128) > maxLeft) maxLeft = abs(bdata[i*2]-128); + if (abs(bdata[i*2+1]-128) > maxRight) maxRight = abs(bdata[i*2+1]-128); + } + } + } + } + + if (maxLeft>=(maxAmp-1)) clipLeft=true; + if (maxRight>=(maxAmp-1)) clipRight=true; + + /* + Draw bars. + */ + buffer->fill(black /* backgroundColor() */); + painter.begin(buffer); + + if (PowervsMax) { /* Power */ + if (LogvsLinear) { /* Log */ + /* we want leftmost to be 100dB + (though signal-to-noise ratio can't be more than 96.33dB in power) + and rightmost to be 0dB (maximum power) */ + float dBvalue=1+0.1*log10(floatPowerLeft); /* 10/100 = 0.1 */ + //fprintf(stderr, "dB: %f\r", -10*log10(floatPowerLeft)); + sprintf(buf, "%.2f dB", -10*log10(floatPowerLeft)); + emit setvalue(buf); + + drawBar(painter, 0, dBvalue, height()/channels, clipLeft); + if (channels == 2) { + dBvalue=1+0.1*log10(floatPowerRight); + drawBar(painter, 1, dBvalue, height()/channels, clipRight); + } + } else { /* Linear */ + drawBar(painter, 0, floatPowerLeft, height()/channels, clipLeft); + if (channels == 2) { + drawBar(painter, 1, floatPowerRight, height()/channels, clipRight); + } + } + } else { /* Max */ + if (LogvsLinear) { /* Log */ + /* we want leftmost to be 50dB + (though signal-to-noise ratio can't be more than 48.16dB in amplitude) + and rightmost to be 0dB (clipping trheshold reached!) */ + float logvalue=1+0.2*log10((float)maxLeft/(float)maxAmp); /* 10/50 = 0.2 */ + drawBar(painter, 0, logvalue, height()/channels, clipLeft); + if (channels == 2) { + logvalue=1+0.2*log10((float)maxRight/(float)maxAmp); + drawBar(painter, 1, logvalue, height()/channels, clipRight); + } + } else { /* Linear */ + float value=((float)maxLeft/(float)maxAmp); + drawBar(painter, 0, value, height()/channels, clipLeft); + if (channels == 2) { + value=((float)maxRight/(float)maxAmp); + drawBar(painter, 1, value, height()/channels, clipRight); + } + } + }; + + painter.end(); + bitBlt(this, 0, 0, buffer); + +} /* paintEvent */ + +void LevelWindow::new_params(struct SOUNDPARAMS *p) +{ + afmt = p->format; + channels = p->channels; + samples = p->blocksize/channels/(afmt == FMT_16BIT ? 2 : 1); + + init = TRUE; + +} /* new_params */ + +void LevelWindow::new_data(void *data) +{ + + sdata = (short int*)data; + repaint(); + +} /* new_data */ diff --git a/level.h b/level.h new file mode 100644 index 0000000..9d15d19 --- /dev/null +++ b/level.h @@ -0,0 +1,56 @@ +/* + * level.h. Part of krecord by Gerd Knorr. + * + * Displays the input level. + * + * Copyright (C) 1998 Florian Kolbe + * + * History see level.cpp + * + */ + +#ifndef LEVEL_H +#define LEVEL_H + +#include + +class LevelWindow : public QWidget +{ + Q_OBJECT; + +public: + LevelWindow(QWidget *parent, char *name); + ~LevelWindow(void); + bool PowervsMax; + bool LogvsLinear; + +public slots: + void new_params(struct SOUNDPARAMS *params); + void new_data(void *data); + void resetPeakLeft(); + void resetPeakRight(); + +protected: + void paintEvent (QPaintEvent *); + void resizeEvent(QResizeEvent *); + +private: + int init; + int afmt; + int samples; + int channels; + signed short* sdata; + QColor orange; + QPixmap* buffer; + QTimer* timer[2]; + float peak[2]; + bool clipLeft; + bool clipRight; + + void drawBar(QPainter& painter, int where, float level, int size, bool drawRed); + +signals: + void setvalue(char *text); +}; + +#endif /* LEVEL_H */ diff --git a/mix.c b/mix.c new file mode 100644 index 0000000..cb9b1df --- /dev/null +++ b/mix.c @@ -0,0 +1,75 @@ +/* + * dump current mixer settings + * + * (c) 1998 Gerd Knorr + * + */ +#include +#include +#include +#include +#include +#include +#include + +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<> 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 +# +# 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 +# +# main features: +# * autodependencies via "cpp -MD" +# * fancy, non-verbose output +# +# This file is public domain. No warranty. If it breaks you keep +# both pieces. +# +######################################################################## + +# verbose yes/no +verbose ?= no + +# dependency files +tmpdep = mk/$(subst /,_,$*).tmp +depfile = mk/$(subst /,_,$*).dep +depfiles = mk/*.dep + +compile_c = $(CC) $(CFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $< +compile_cc = $(CXX) $(CXXFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $< +fixup_deps = sed -e "s|.*\.o:|$@:|" < $(tmpdep) > $(depfile) && rm -f $(tmpdep) +cc_makedirs = mkdir -p $(dir $@) $(dir $(depfile)) + +link_app = $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) +link_so = $(CC) $(LDFLAGS) -shared -Wl,-soname,$(@F) -o $@ $^ $(LDLIBS) +ar_lib = rm -f $@ && ar -r $@ $^ && ranlib $@ + +moc_h = $(MOC) $< -o $@ +msgfmt_po = msgfmt -o $@ $< + +# non-verbose output +ifeq ($(verbose),no) + echo_compile_c = echo " CC " $@ + echo_compile_cc = echo " CXX " $@ + echo_link_app = echo " LD " $@ + echo_link_so = echo " LD " $@ + echo_ar_lib = echo " AR " $@ + echo_moc_h = echo " MOC " $@ + echo_msgfmt_po = echo " MSGFMT " $@ +else + echo_compile_c = echo $(compile_c) + echo_compile_cc = echo $(compile_cc) + echo_link_app = echo $(link_app) + echo_link_so = echo $(link_so) + echo_ar_lib = echo $(ar_lib) + echo_moc_h = echo $(moc_h) + echo_msgfmt_po = echo $(msgfmt_po) +endif + +%.o: %.c + @$(cc_makedirs) + @$(echo_compile_c) + @$(compile_c) + @$(fixup_deps) + +%.o: %.cc + @$(cc_makedirs) + @$(echo_compile_cc) + @$(compile_cc) + @$(fixup_deps) + +%.o: %.cpp + @$(cc_makedirs) + @$(echo_compile_cc) + @$(compile_cc) + @$(fixup_deps) + + +%.so: %.o + @$(echo_link_so) + @$(link_so) + +%: %.o + @$(echo_link_app) + @$(link_app) + +%.moc : %.h + @$(echo_moc_h) + @$(moc_h) + +%.mo : %.po + @$(echo_msgfmt_po) + @$(msgfmt_po) + diff --git a/mk/Maintainer.mk b/mk/Maintainer.mk new file mode 100644 index 0000000..5bf9480 --- /dev/null +++ b/mk/Maintainer.mk @@ -0,0 +1,12 @@ +# just some maintainer stuff for me ... +######################################################################## + +make-sync-dir = $(HOME)/src/gnu-make + +.PHONY: sync +sync:: distclean + test -d $(make-sync-dir) + rm -f $(srcdir)/INSTALL $(srcdir)/mk/*.mk + cp -v $(make-sync-dir)/INSTALL $(srcdir)/. + cp -v $(make-sync-dir)/*.mk $(srcdir)/mk + chmod 444 $(srcdir)/INSTALL $(srcdir)/mk/*.mk diff --git a/mk/Variables.mk b/mk/Variables.mk new file mode 100644 index 0000000..930f824 --- /dev/null +++ b/mk/Variables.mk @@ -0,0 +1,46 @@ +# common variables ... +######################################################################## + +# directories +DESTDIR = +srcdir ?= . +prefix ?= /usr/local +bindir = $(DESTDIR)$(prefix)/bin +mandir = $(DESTDIR)$(prefix)/share/man +locdir = $(DESTDIR)$(prefix)/share/locale + +# package + version +empty := +space := $(empty) $(empty) +ifneq ($(wildcard $(srcdir)/VERSION),) + VERSION := $(shell cat $(srcdir)/VERSION) +else + VERSION := 42 +endif + +# programs +CC ?= gcc +CXX ?= g++ +MOC ?= $(if $(QTDIR),$(QTDIR)/bin/moc,moc) +INSTALL ?= install +INSTALL_BINARY := $(INSTALL) -s +INSTALL_SCRIPT := $(INSTALL) +INSTALL_DATA := $(INSTALL) -m 644 +INSTALL_DIR := $(INSTALL) -d + +# cflags +CFLAGS ?= -g -O2 +CFLAGS += -Wall -Wmissing-prototypes -Wstrict-prototypes \ + -Wpointer-arith -Wunused + +# add /usr/local to the search path if something is in there ... +ifneq ($(wildcard /usr/local/include/*.h),) + CFLAGS += -I/usr/local/include + LDFLAGS += -L/usr/local/$(LIB) +endif + +# fixup include path for $(srcdir) != "." +ifneq ($(srcdir),.) + CFLAGS += -I. -I$(srcdir) +endif + diff --git a/oss.cpp b/oss.cpp new file mode 100644 index 0000000..af41cf2 --- /dev/null +++ b/oss.cpp @@ -0,0 +1,358 @@ +#if defined(HAVE_SOUNDCARD_H) || defined(HAVE_SYS_SOUNDCARD_H) + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SOUNDCARD_H +# include +#endif +#ifdef HAVE_SYS_SOUNDCARD_H +# include +#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<>16) & 0xff,(i>>8) & 0xff,(i>>4) & 0xf,(i&0xf)+'a'); + } + + close(dsp); + init_done = 1; + + } else { + init_done = 0; + } +} + +int +Soundcard::has_channels() +{ + if (!init_done) + return -1; + return channels_hw; +} + +int +Soundcard::has_format(int f) +{ + if (!init_done) + return -1; + switch (f) { + case FMT_8BIT: + return (afmt_hw & AFMT_U8) ? 1 : 0; + break; + case FMT_16BIT: + return (afmt_hw & AFMT_S16_LE) ? 1 : 0; + break; + case FMT_MULAW: + case FMT_ALAW: + default: + return 0; + } +} + +char* +Soundcard::driver() +{ + return driver_name; +} + +int +Soundcard::open_dev(int record) +{ + struct SOUNDPARAMS p; + int frag,rrate; + + if (-1 == (fd = open(devname,record ? O_RDONLY : O_WRONLY))) + goto err; + fcntl(fd,F_SETFD,FD_CLOEXEC); + + /* try to get ~50 ms latency */ + blocksize = 50*channels*rate/1000; + if (afmt == AFMT_U16_BE || afmt == AFMT_S16_BE || + afmt == AFMT_U16_LE || afmt == AFMT_S16_LE) + blocksize *= 2; + for (frag = 0; blocksize != 1; frag++) + blocksize >>= 1; +#if 0 + fprintf(stderr,"asking for %d byte blocksize\n",1 << frag); +#endif + frag |= 0x7fff0000; + if (-1 == ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag)) + perror("ioctl SNDCTL_DSP_SETFRAGMENT"); + + rrate = rate; + if (-1 == ioctl(fd, SNDCTL_DSP_SETFMT, &afmt)) { + perror("ioctl SNDCTL_DSP_SETFMT"); + goto err; + } + if (-1 == ioctl(fd, SNDCTL_DSP_CHANNELS, &channels)) { + perror("ioctl SNDCTL_DSP_SETFMT"); + goto err; + } + if (-1 == ioctl(fd, SNDCTL_DSP_SPEED, &rrate)) { + perror("ioctl SNDCTL_DSP_SETFMT"); + goto err; + } + if (-1 == ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize)) { + perror("ioctl SNDCTL_DSP_SETFMT"); + goto err; + } + if (0 == blocksize) + blocksize = 4096; + if (rrate != rate) { + fprintf(stderr,"sample rate: asked for %d, hardware uses %d. ", + rate,rrate); + if (abs(rate-rrate)*100 < rate) { + fprintf(stderr,"that's fine (diff <1%%).\n"); + } else { + fprintf(stderr,"way off, using hardware rate.\n"); + rate = rrate; + } + } + + latency = blocksize*1000/channels/rate; + if (afmt == AFMT_U16_BE || afmt == AFMT_S16_BE || + afmt == AFMT_U16_LE || afmt == AFMT_S16_LE) + latency = latency/2; + + telmi = new QSocketNotifier + (fd, record ? QSocketNotifier::Read : QSocketNotifier::Write); + QObject::connect(telmi,SIGNAL(activated(int)), + this, SLOT(sounddata(int))); + + stat = record ? STATUS_RECORD : STATUS_PLAYBACK; +#if 0 + fprintf(stderr,"%s (format=%d, %s, rate=%d, blocksize=%d, latency=%d ms)\n", + record ? "recording" : "playback", + afmt, + (channels == 2) ? "stereo" : "mono", + rate, blocksize, latency); +#endif + p.channels = channels; + p.rate = rate; + p.blocksize = blocksize; + p.latency = latency; + switch (afmt) { + case AFMT_U8: p.format = FMT_8BIT; break; + case AFMT_S16_LE: p.format = FMT_16BIT; break; + default: fprintf(stderr,"oops(open): unsupported sound format\n"); exit(1); + } + emit newparams(&p); + + if (record) { + trigger = ~PCM_ENABLE_INPUT; + ioctl(fd,SNDCTL_DSP_SETTRIGGER,&trigger); + trigger = PCM_ENABLE_INPUT; + ioctl(fd,SNDCTL_DSP_SETTRIGGER,&trigger); + } + return 0; + +err: + if (-1 != fd) + close(fd); + stat = STATUS_CLOSED; + fd = -1; + return -1; +} + +void +Soundcard::close_dev() +{ + close(fd); + fd = -1; + stat = STATUS_CLOSED; + + delete telmi; + return; +} + +void +Soundcard::setparams(struct SOUNDPARAMS *p) +{ + rate = p->rate; + channels = p->channels; + switch (p->format) { + case FMT_8BIT: afmt = AFMT_U8; break; + case FMT_16BIT: afmt = AFMT_S16_LE; break; + default: fprintf(stderr,"oops(set): unsupported sound format\n"); exit(1); + } + + switch (stat) { + case STATUS_RECORD: + close_dev(); + open_dev(TRUE); + break; + case STATUS_PLAYBACK: + close_dev(); + open_dev(FALSE); + break; + case STATUS_CLOSED: + if (!init_done) + get_capabilities(); + if (!init_done) + return; + if (0 == open_dev(TRUE)) + close_dev(); + break; + } +} + +void +Soundcard::sounddata(int s) +{ + int rc,have; + + switch (stat) { + case STATUS_RECORD: + /* read */ + for (have = 0; have < blocksize;) { + rc = read(fd,buffer+have,blocksize-have); + switch (rc) { + case -1: + if (EINTR != errno) { + perror("read sound"); + exit(1); + } + break; + case 0: + fprintf(stderr,"Huh? got 0 bytes from sound device?\n"); + exit(1); + default: + have += rc; + } + } + emit senddata((void*)buffer); + break; + case STATUS_PLAYBACK: + emit receivedata((void*)buffer); + if (-1 != fd) + write(fd,buffer,blocksize); + emit senddata((void*)buffer); /* fft :-) */ + break; + } +} + +#endif /* SOUNDCARD_H */ diff --git a/oss.h b/oss.h new file mode 100644 index 0000000..1cb128b --- /dev/null +++ b/oss.h @@ -0,0 +1,68 @@ +#ifndef OSS_H +#define OSS_H + +#include +#include + +/* ---------------------------------------------------------------------- */ + +#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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; +} diff --git a/sound.h b/sound.h new file mode 100644 index 0000000..b386c9f --- /dev/null +++ b/sound.h @@ -0,0 +1,76 @@ +#ifndef SOUND_H +#define SOUND_H + +#include +#include +#include +#include + +#include + +#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 +#include +#include + +#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;i0;mask >>= 1) + temp=(temp >> 1) + (i&mask ? Points/2 : 0); + + BitReversed[i]=temp; + } + + for(i=0;i32767.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> 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 +#include +#include +#include +#include +#include + +#include +#include + +/* for ioctl(I_FLUSH) */ +#include +#include + +#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 +#include + +/* ---------------------------------------------------------------------- */ + +#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 -- cgit