Commit e4fff429 authored by Jaromír Mikeš's avatar Jaromír Mikeš

Imported Upstream version 0.12.2

parents
This diff is collapsed.
2009-09-13: klick 0.12.2
* Fixed a segfault (introduced in 0.12.0) that sometimes occured when
terminating klick.
2009-07-10: klick 0.12.1
* Fixed a build error on amd64.
2009-07-09: klick 0.12.0
* Added click track export functionality.
* SConstruct now supports DESTDIR option to make packagers' lifes easier.
2009-01-14: klick 0.11.0
* Many OSC related fixes and improvements.
* Optionally use Rubber Band for pitch shifting (build with
RUBBERBAND=True).
2008-12-03: klick 0.10.0
* Several new OSC methods, most notably for managing JACK connections.
* Various minor fixes, some code cleanup.
* Included a proper user manual in the tarball.
2008-10-23: klick 0.9.1
* Fixed a bug that restricted the tempo to tempo_limit even when
tempo_increment was disabled.
2008-10-17: klick 0.9.0
* New OSC methods: set_tempo_increment, set_tempo_limit, set_pattern.
* Added optional return address parameter to all query methods.
2008-08-11: klick 0.8.0
* Added basic OSC support.
* Added interactive mode (klick -i).
2008-05-24: klick 0.7.0
* New default sound (square wave).
* Allow volume and pitch of emphasized and normal beats to be adjusted
individually.
* Provide samples as regular wave files, rather than compiling them into
the executable.
* Some source code restructuring.
2008-04-08: klick 0.6.3
* Fixed a bug that caused klick to play at a slightly wrong tempo when
using silent beats (with the pattern feature).
* Made klick compile cleanly with -W -Wall.
2008-03-19: klick 0.6.2
* Fixed a bug when using -E or -e and either -v or -w parameters at the
same time.
* Fixed (and actually documented) tempo-per-beat feature.
* Use jack_client_open() instead of deprecated jack_client_new(), default
client name is now simply "klick".
2008-02-02: klick 0.6.1
* Fixed parsing of patterns on the command line.
* Don't choke on tempo map entries where tempo == tempo2.
2008-01-27: klick 0.6
* Fixed a segfault when changing the position via JACK transport.
* Vastly more efficient seeking in the tempo map.
* Fixed erroneous behaviour of JACK transport BBT slave mode (-j), and
be a little more tolerant of faulty BBT information from the timebase
master.
* Try to send more sensible BBT information in timebase master mode.
2007-12-02: klick 0.5
* Additional sound (bell/click).
* Added option to auto-connect to hardware output ports.
* Various minor fixes and enhancements, several changes under the hood.
* Improved documentation.
2007-07-08: klick 0.4
* JACK transport master support.
* JACK transport slave support (running without tempomap).
* Added ability to change the pitch of the click sounds.
* Volume and tempo multipliers are now given as floats rather than
percentage.
* Hopefully more bugs fixed than newly introduced.
2007-03-26: klick 0.2
* Some bugfixes.
* Improved documentation.
2007-02-10: klick 0.1
* Initial release.
klick - an advanced metronome for jack
Copyright (C) 2007-2009 Dominic Sacré <dominic.sacre@gmx.de>
Some of the audio samples used by klick have been borrowed (under
the terms of the GPL) from:
* Ardour [http://ardour.org/], sounds by Nick Mainsbridge
* GTick [http://www.antcom.de/gtick/], sounds by Roland Stigge
* Freepats [http://freepats.zenvoid.org/]
Requirements:
=============
* JACK Audio Connection Kit [http://jackaudio.org/]
* Boost [http://www.boost.org/] (headers only)
* libsamplerate [http://www.mega-nerd.com/SRC/]
* libsndfile [http://www.mega-nerd.com/libsndfile/]
Optional:
=========
* liblo [http://liblo.sourceforge.net/]
* Rubber Band [http://www.breakfastquay.com/rubberband/]
Installation:
=============
In the klick source directory, run:
scons
and then, as root:
scons install
Documentation:
==============
See doc/manual.html for the klick user manual.
# -*- python -*-
import os
version = '0.12.2'
env = Environment(
CPPDEFINES = [
('VERSION', '\\"%s\\"' % version),
],
ENV = os.environ,
)
# build options
opts = Options('scache.conf')
opts.AddOptions(
PathOption('PREFIX', 'install prefix', '/usr/local'),
PathOption('DESTDIR', 'intermediate install prefix', '', PathOption.PathAccept),
BoolOption('DEBUG', 'debug mode', False),
BoolOption('OSC', 'OSC support', True),
BoolOption('TERMINAL', 'terminal control support', True),
BoolOption('RUBBERBAND', 'use Rubber Band for pitch shifting', False),
)
opts.Update(env)
opts.Save('scache.conf', env)
Help(opts.GenerateHelpText(env))
if env['DEBUG']:
env.Append(CCFLAGS = ['-g', '-W', '-Wall'])
else:
env.Append(CCFLAGS = ['-O2', '-W', '-Wall'])
env.Prepend(CPPDEFINES = 'NDEBUG')
# install paths
prefix_bin = os.path.join(env['PREFIX'], 'bin')
prefix_share = os.path.join(env['PREFIX'], 'share/klick')
env.Append(CPPDEFINES = ('DATA_DIR', '\\"%s\\"' % prefix_share))
# required libraries
env.ParseConfig(
'pkg-config --cflags --libs jack samplerate sndfile'
)
if os.system('pkg-config --atleast-version=1.0.18 sndfile') == 0:
env.Append(CPPDEFINES = ['HAVE_SNDFILE_OGG'])
# source files
sources = [
'src/main.cc',
'src/klick.cc',
'src/options.cc',
'src/audio_interface.cc',
'src/audio_interface_jack.cc',
'src/audio_interface_sndfile.cc',
'src/audio_chunk.cc',
'src/tempomap.cc',
'src/metronome.cc',
'src/metronome_simple.cc',
'src/metronome_map.cc',
'src/metronome_jack.cc',
'src/position.cc',
'src/util/util.cc'
]
# audio samples
samples = [
'samples/square_emphasis.wav',
'samples/square_normal.wav',
'samples/sine_emphasis.wav',
'samples/sine_normal.wav',
'samples/noise_emphasis.wav',
'samples/noise_normal.wav',
'samples/click_emphasis.wav',
'samples/click_normal.wav',
]
# build options
if env['OSC']:
env.ParseConfig('pkg-config --cflags --libs liblo')
env.Append(CPPDEFINES = ['ENABLE_OSC'])
sources += [
'src/osc_interface.cc',
'src/osc_handler.cc',
]
if env['TERMINAL']:
env.Append(CPPDEFINES = ['ENABLE_TERMINAL'])
sources += [
'src/terminal_handler.cc',
]
if env['RUBBERBAND']:
env.ParseConfig('pkg-config --cflags --libs rubberband')
env.Append(CPPDEFINES = ['ENABLE_RUBBERBAND'])
env.Program('klick', sources)
Default('klick')
# installation
env.Alias('install', [
env.Install(env['DESTDIR'] + prefix_bin, 'klick'),
env.Install(env['DESTDIR'] + os.path.join(prefix_share, 'samples'), samples)
])
This diff is collapsed.
/*
* klick - an advanced metronome for jack
*
* Copyright (C) 2007-2009 Dominic Sacré <dominic.sacre@gmx.de>
*
* 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.
*/
#ifndef _AUDIO_HH
#define _AUDIO_HH
#include <jack/types.h>
#include <jack/transport.h>
#include <boost/shared_ptr.hpp>
typedef jack_default_audio_sample_t sample_t;
typedef jack_nframes_t nframes_t;
typedef jack_position_t position_t;
typedef boost::shared_ptr<class AudioChunk> AudioChunkPtr;
typedef boost::shared_ptr<class AudioChunk const> AudioChunkConstPtr;
#endif // _AUDIO_HH
/*
* klick - an advanced metronome for jack
*
* Copyright (C) 2007-2009 Dominic Sacré <dominic.sacre@gmx.de>
*
* 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.
*/
#include "audio_chunk.hh"
#include <sstream>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <algorithm>
#include <samplerate.h>
#include <sndfile.h>
#ifdef ENABLE_RUBBERBAND
#include <rubberband/RubberBandStretcher.h>
#endif
#include "util/string.hh"
#include "util/debug.hh"
AudioChunk::AudioChunk(std::string const & filename, nframes_t samplerate)
{
SF_INFO sfinfo;
std::memset(&sfinfo, 0, sizeof(sfinfo));
SNDFILE *f;
if ((f = sf_open(filename.c_str(), SFM_READ, &sfinfo)) == NULL) {
throw std::runtime_error(das::make_string() << "failed to open audio file '" << filename << "'");
}
_samples.reset(new sample_t[sfinfo.frames * sfinfo.channels]);
_length = sfinfo.frames;
_samplerate = sfinfo.samplerate;
sf_readf_float(f, _samples.get(), sfinfo.frames);
// convert stereo to mono
if (sfinfo.channels == 2) {
SamplePtr mono_samples(new sample_t[sfinfo.frames]);
for (int i = 0; i < sfinfo.frames; i++) {
mono_samples[i] = (_samples[i*2] + _samples[i*2 + 1]) / 2;
}
_samples = mono_samples;
}
// convert samplerate
if (_samplerate != samplerate) {
resample(samplerate);
}
sf_close(f);
}
void AudioChunk::adjust_volume(float volume)
{
if (volume == 1.0f) return;
for (nframes_t i = 0; i < _length; i++) {
_samples[i] *= volume;
}
}
void AudioChunk::adjust_pitch(float factor)
{
if (factor == 1.0f || !_length) return;
#ifdef ENABLE_RUBBERBAND
pitch_shift(factor);
#else
nframes_t s = _samplerate;
resample(static_cast<nframes_t>(_samplerate / factor));
_samplerate = s;
#endif
}
void AudioChunk::resample(nframes_t samplerate)
{
SRC_DATA src_data;
int error;
src_data.input_frames = _length;
src_data.data_in = _samples.get();
src_data.src_ratio = static_cast<float>(samplerate) / static_cast<float>(_samplerate);
src_data.output_frames = std::max(static_cast<long>(_length * src_data.src_ratio), 1L);
SamplePtr samples_new(new sample_t[src_data.output_frames]());
src_data.data_out = samples_new.get();
if ((error = src_simple(&src_data, SRC_SINC_BEST_QUALITY, 1)) != 0) {
throw std::runtime_error(das::make_string() << "error converting samplerate: " << src_strerror(error));
}
_samples = samples_new;
_length = src_data.output_frames;
_samplerate = samplerate;
}
#ifdef ENABLE_RUBBERBAND
void AudioChunk::pitch_shift(float factor)
{
nframes_t const BLOCK_SIZE = 1024;
RubberBand::RubberBandStretcher rb(_samplerate, 1, RubberBand::RubberBandStretcher::PercussiveOptions);
rb.setPitchScale(factor);
rb.setExpectedInputDuration(_length);
SamplePtr samples_new(new sample_t[_length]());
sample_t *buf;
nframes_t k = 0;
// study all samples
buf = _samples.get();
rb.study(&buf, _length, true);
for (nframes_t i = 0; i < _length; i += BLOCK_SIZE)
{
// process one block of samples
buf = _samples.get() + i;
nframes_t s = std::min(BLOCK_SIZE, _length - i);
rb.process(&buf, s, i + BLOCK_SIZE >= _length);
// retrieve available samples
buf = samples_new.get() + k;
s = std::min(static_cast<nframes_t>(rb.available()), _length - k);
if (s > 0) {
k += rb.retrieve(&buf, s);
}
}
ASSERT(rb.available() == -1);
_length = k;
_samples = samples_new;
}
#endif
/*
* klick - an advanced metronome for jack
*
* Copyright (C) 2007-2009 Dominic Sacré <dominic.sacre@gmx.de>
*
* 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.
*/
#ifndef _AUDIO_CHUNK_HH
#define _AUDIO_CHUNK_HH
#include "audio.hh"
#include <boost/shared_array.hpp>
#include "util/disposable.hh"
/*
* mono 32-bit float audio sample
*/
class AudioChunk
: public das::disposable
{
public:
// loads sample from file, converting to the given samplerate if samplerate is non-zero
AudioChunk(std::string const & filename, nframes_t samplerate);
// create empty audio
AudioChunk(nframes_t samplerate)
: _samples()
, _length(0)
, _samplerate(samplerate)
{
}
void adjust_volume(float volume);
void adjust_pitch(float factor);
sample_t const * samples() const { return _samples.get(); }
nframes_t length() const { return _length; }
nframes_t samplerate() const { return _samplerate; }
private:
typedef boost::shared_array<sample_t> SamplePtr;
void resample(nframes_t samplerate);
#ifdef ENABLE_RUBBERBAND
void pitch_shift(float factor);
#endif
SamplePtr _samples;
nframes_t _length;
nframes_t _samplerate;
};
#endif // _AUDIO_CHUNK_HH
/*
* klick - an advanced metronome for jack
*
* Copyright (C) 2007-2009 Dominic Sacré <dominic.sacre@gmx.de>
*
* 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.
*/
#include "audio_interface.hh"
#include "audio_chunk.hh"
#include "util/debug.hh"
AudioInterface::AudioInterface()
: _next_chunk(0)
, _volume(1.0f)
{
}
void AudioInterface::set_process_callback(ProcessCallback cb)
{
_process_cb = cb;
}
void AudioInterface::play(AudioChunkConstPtr chunk, nframes_t offset, float volume)
{
ASSERT(chunk->samplerate() == samplerate());
_chunks[_next_chunk].chunk = chunk;
_chunks[_next_chunk].offset = offset;
_chunks[_next_chunk].pos = 0;
_chunks[_next_chunk].volume = volume;
_next_chunk = (_next_chunk + 1) % _chunks.size();
}
void AudioInterface::process_mix(sample_t *buffer, nframes_t nframes)
{
for (ChunkArray::iterator i = _chunks.begin(); i != _chunks.end(); ++i)
{
if (i->chunk) {
process_mix_samples(buffer + i->offset,
i->chunk->samples() + i->pos,
std::min(nframes - i->offset, i->chunk->length() - i->pos),
i->volume * _volume);
i->pos += nframes - i->offset;
i->offset = 0;
if (i->pos >= i->chunk->length()) {
i->chunk.reset();
}
}
}
}
void AudioInterface::process_mix_samples(sample_t *dest, sample_t const * src, nframes_t length, float volume)
{
for (sample_t *end = dest + length; dest < end; dest++, src++) {
*dest += *src * volume;
}
}
/*
* klick - an advanced metronome for jack
*
* Copyright (C) 2007-2009 Dominic Sacré <dominic.sacre@gmx.de>
*
* 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.
*/
#ifndef _AUDIO_INTERFACE_HH
#define _AUDIO_INTERFACE_HH
#include "audio.hh"
#include <string>
#include <stdexcept>
#include <boost/array.hpp>
#include <boost/noncopyable.hpp>
#include <boost/function.hpp>
class AudioInterface
: boost::noncopyable
{
public:
struct AudioError : public std::runtime_error {
AudioError(std::string const & w) : std::runtime_error(w) { }
};
AudioInterface();
virtual ~AudioInterface() { }
typedef boost::function<void (sample_t *, nframes_t)> ProcessCallback;
virtual void set_process_callback(ProcessCallback cb);
// get sample rate
virtual nframes_t samplerate() const = 0;
// check if backend is still running
virtual bool is_shutdown() const = 0;
// start playing audio chunk at offset into the current period
void play(AudioChunkConstPtr chunk, nframes_t offset, float volume = 1.0);
void set_volume(float v) { _volume = v; }
float volume() const { return _volume; }
protected:
ProcessCallback _process_cb;
void process_mix(sample_t *, nframes_t);
private:
void process_mix_samples(sample_t *dest, sample_t const * src, nframes_t length, float volume = 1.0);
// maximum number of audio chunks that can be played simultaneously
static int const MAX_PLAYING_CHUNKS = 4;
struct PlayingChunk {
AudioChunkConstPtr chunk;
nframes_t offset;
nframes_t pos;
float volume;
};
typedef boost::array<PlayingChunk, MAX_PLAYING_CHUNKS> ChunkArray;
ChunkArray _chunks;
int _next_chunk;
float _volume;
};
class AudioInterfaceTransport
: public AudioInterface
{
public:
typedef boost::function<void (position_t *)> TimebaseCallback;
virtual void set_timebase_callback(TimebaseCallback cb) = 0;
virtual bool transport_rolling() const = 0;
virtual position_t position() const = 0;
virtual nframes_t frame() const = 0;
virtual bool set_position(position_t const &) = 0;
virtual bool set_frame(nframes_t) = 0;
protected:
TimebaseCallback _timebase_cb;
};
#endif // _AUDIO_INTERFACE_HH
/*
* klick - an advanced metronome for jack
*
* Copyright (C) 2007-2009 Dominic Sacré <dominic.sacre@gmx.de>
*
* 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.
*/
#include "audio_interface_jack.hh"
#include <jack/jack.h>
#include <jack/transport.h>
#include <iostream>
#include <cerrno>
#include <cstring>
#include "util/string.hh"
#include "util/debug.hh"
AudioInterfaceJack::AudioInterfaceJack(std::string const & name)
: _shutdown(false)
{
if ((_client = jack_client_open(name.c_str(), JackNullOption, NULL)) == 0) {
throw AudioError("can't connect to jack server");
}
jack_set_process_callback(_client, &process_callback_, static_cast<void*>(this));
jack_on_shutdown(_client, &shutdown_callback_, static_cast<void*>(this));
if ((_output_port = jack_port_register(_client, "out", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)) == NULL) {
throw AudioError("can't register output port");
}
if (jack_activate(_client)) {
throw AudioError("can't activate client");
}
}
AudioInterfaceJack::~AudioInterfaceJack()
{
jack_deactivate(_client);
jack_client_close(_client);
}
std::string AudioInterfaceJack::client_name() const
{
return std::string(jack_get_client_name(_client));
}
pthread_t AudioInterfaceJack::client_thread() const
{
return jack_client_thread_id(_client);
}