Add live shifter to JNI

This commit is contained in:
Chris Cannam
2024-10-02 18:22:06 +01:00
parent 4353ddd78d
commit 923a196f8f
10 changed files with 410 additions and 20 deletions

View File

@@ -31,3 +31,4 @@ playlist-out/*
formant-out-*/
out*.wav
packages/
otherbuilds/docker/Dockerfile

View File

@@ -0,0 +1,70 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
/*
Rubber Band Library
An audio time-stretching and pitch-shifting library.
Copyright 2007-2022 Particular Programs Ltd.
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. See the file
COPYING included with this distribution for more information.
Alternatively, if you have a valid commercial licence for the
Rubber Band Library obtained by agreement with the copyright
holders, you may redistribute and/or modify it under the terms
described in that licence.
If you wish to distribute code using the Rubber Band Library
under terms other than those of the GNU General Public License,
you must obtain a valid commercial licence before doing so.
*/
package com.breakfastquay.rubberband;
public class RubberBandLiveShifter
{
public RubberBandLiveShifter(int sampleRate, int channels,
int options) {
handle = 0;
initialise(sampleRate, channels, options);
}
public native void dispose();
public native void reset();
public native void setPitchScale(double scale);
public native int getChannelCount();
public native double getPitchScale();
public native int getStartDelay();
public native void setFormantOption(int options);
public native int getBlockSize();
public native void shift(float[][] input, int inOffset, float[][] output, int outOffset);
public void shift(float[][] input, float[][] output) {
shift(input, 0, output, 0);
}
private native void initialise(int sampleRate, int channels, int options);
private long handle;
public static final int OptionWindowShort = 0x00000000;
public static final int OptionWindowMedium = 0x00100000;
public static final int OptionFormantShifted = 0x00000000;
public static final int OptionFormantPreserved = 0x01000000;
public static final int OptionChannelsApart = 0x00000000;
public static final int OptionChannelsTogether = 0x10000000;
static {
System.loadLibrary("rubberband-jni");
}
};

View File

@@ -2,14 +2,14 @@
package com.breakfastquay.rubberband.test;
import com.breakfastquay.rubberband.RubberBandStretcher;
import com.breakfastquay.rubberband.RubberBandLiveShifter;
import java.util.TreeMap;
public class RubberBandTest
{
public static void main(String[] args) {
public static void exerciseStretcher() {
int channels = 1;
int rate = 44100;
@@ -72,6 +72,9 @@ public class RubberBandTest
i0 = 0;
double sqrtotal = 0.0;
int n = 0;
for (int block = 0; block < blocks; ++block) {
for (int c = 0; c < channels; ++c) {
@@ -98,13 +101,88 @@ public class RubberBandTest
}
int obtained = stretcher.retrieve(buffer, 0, requested);
for (int i = 0; i < obtained; ++i) {
System.out.println(Float.toString(buffer[0][i]));
sqrtotal += (double)(buffer[0][i] * buffer[0][i]);
++n;
}
}
}
System.err.println
(String.format("in = %d, out = %d, rms = %f",
blocksize * blocks, n,
Math.sqrt(sqrtotal / (double)n)));
stretcher.dispose();
}
public static void exerciseLiveShifter() {
int channels = 1;
int rate = 44100;
RubberBandLiveShifter shifter = new RubberBandLiveShifter
(rate,
channels,
0);
shifter.setPitchScale(0.8);
System.err.println
(String.format("Channel count: %d\n" +
"Pitch scale: %f\n" +
"Block size: %d\n" +
"Start delay: %d",
shifter.getChannelCount(),
shifter.getPitchScale(),
shifter.getBlockSize(),
shifter.getStartDelay()
));
int blocksize = shifter.getBlockSize();
int blocks = 400;
double freq = 440.0;
float[][] inbuf = new float[channels][blocksize];
float[][] outbuf = new float[channels][blocksize];
int i0 = 0;
double sqrtotal = 0.0;
int n = 0;
for (int block = 0; block < blocks; ++block) {
for (int c = 0; c < channels; ++c) {
for (int i = 0; i < blocksize; ++i) {
inbuf[c][i] = (float)Math.sin
((double)i0 * freq * Math.PI * 2.0 / (double)rate);
if (i0 % rate == 0) {
inbuf[c][i] = 1.f;
}
++i0;
}
}
shifter.shift(inbuf, outbuf);
for (int i = 0; i < blocksize; ++i) {
sqrtotal += (double)(outbuf[0][i] * outbuf[0][i]);
++n;
}
}
System.err.println
(String.format("in = %d, out = %d, rms = %f",
blocksize * blocks, n,
Math.sqrt(sqrtotal / (double)n)));
shifter.dispose();
}
public static void main(String[] args) {
exerciseStretcher();
exerciseLiveShifter();
}
}

View File

@@ -63,6 +63,7 @@ jni_sources = [
java_sources = [
'com/breakfastquay/rubberband/RubberBandStretcher.java',
'com/breakfastquay/rubberband/RubberBandLiveShifter.java',
]
java_test_sources = [

View File

@@ -0,0 +1,32 @@
FROM ubuntu:22.04
MAINTAINER Chris Cannam <cannam@all-day-breakfast.com>
RUN apt-get update && \
apt-get install -y \
software-properties-common \
build-essential \
pkg-config \
libsamplerate0-dev \
libsndfile1-dev \
libfftw3-dev \
ladspa-sdk \
lv2-dev \
vamp-plugin-sdk \
libboost-test-dev \
mercurial \
meson \
ninja-build \
openjdk-8-jre \
openjdk-8-jdk
WORKDIR /root
RUN hg clone -u [[REVISION]] https://hg.sr.ht/~breakfastquay/rubberband
WORKDIR rubberband
RUN meson setup build
RUN ninja -C build
RUN meson test -C build
WORKDIR build
RUN java -Djava.library.path=$(pwd) -cp rubberband-test.jar com.breakfastquay.rubberband.test.RubberBandTest

8
otherbuilds/docker/run.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
revision=$(hg id | sed 's/[^0-9a-z].*$//')
cat Dockerfile.in | perl -p -e "s/\[\[REVISION\]\]/$revision/g" > Dockerfile
sudo docker build -f Dockerfile .

View File

@@ -293,6 +293,9 @@ public:
* array having enough room to store n samples where n is the value
* returned by getBlockSize().
*
* The input and output must be separate arrays; they cannot alias
* one another or overlap.
*
* Sample values are conventionally expected to be in the range
* -1.0f to +1.0f.
*/

View File

@@ -197,7 +197,7 @@ RB_EXTERN unsigned int rubberband_live_get_start_delay(const RubberBandLiveState
RB_EXTERN void rubberband_live_set_formant_option(RubberBandLiveState, RubberBandOptions options);
RB_EXTERN unsigned int rubberband_live_get_block_size(RubberBandLiveState, RubberBandOptions options);
RB_EXTERN unsigned int rubberband_live_get_block_size(RubberBandLiveState);
RB_EXTERN void rubberband_live_shift(RubberBandLiveState, const float *const *input, float *const *output);

View File

@@ -22,6 +22,7 @@
*/
#include "rubberband/RubberBandStretcher.h"
#include "rubberband/RubberBandLiveShifter.h"
#include "common/Allocators.h"
@@ -231,6 +232,86 @@ JNIEXPORT jint JNICALL Java_com_breakfastquay_rubberband_RubberBandStretcher_ret
JNIEXPORT void JNICALL Java_com_breakfastquay_rubberband_RubberBandStretcher_initialise
(JNIEnv *, jobject, jint, jint, jint, jdouble, jdouble);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: dispose
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_dispose
(JNIEnv *, jobject);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: reset
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_reset
(JNIEnv *, jobject);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: setPitchScale
* Signature: (D)V
*/
JNIEXPORT void JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_setPitchScale
(JNIEnv *, jobject, jdouble);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: getChannelCount
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_getChannelCount
(JNIEnv *, jobject);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: getPitchScale
* Signature: ()D
*/
JNIEXPORT jdouble JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_getPitchScale
(JNIEnv *, jobject);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: getStartDelay
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_getStartDelay
(JNIEnv *, jobject);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: setFormantOption
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_setFormantOption
(JNIEnv *, jobject, jint);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: getBlockSize
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_getBlockSize
(JNIEnv *, jobject);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: shift
* Signature: ([[FI[[FI)V
*/
JNIEXPORT void JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_shift
(JNIEnv *, jobject, jobjectArray, jint, jobjectArray, jint);
/*
* Class: com_breakfastquay_rubberband_RubberBandLiveShifter
* Method: initialise
* Signature: (III)V
*/
JNIEXPORT void JNICALL Java_com_breakfastquay_rubberband_RubberBandLiveShifter_initialise
(JNIEnv *, jobject, jint, jint, jint);
}
RubberBandStretcher *
@@ -390,45 +471,48 @@ Java_com_breakfastquay_rubberband_RubberBandStretcher_setKeyFrameMap(JNIEnv *env
}
JNIEXPORT void JNICALL
Java_com_breakfastquay_rubberband_RubberBandStretcher_study(JNIEnv *env, jobject obj, jobjectArray data, jint offset, jint n, jboolean final)
Java_com_breakfastquay_rubberband_RubberBandStretcher_study(JNIEnv *env, jobject obj, jobjectArray input, jint offset, jint n, jboolean final)
{
int channels = env->GetArrayLength(data);
int channels = env->GetArrayLength(input);
float **arr = allocate<float *>(channels);
float **input = allocate<float *>(channels);
float **inbuf = allocate<float *>(channels);
for (int c = 0; c < channels; ++c) {
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(data, c);
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(input, c);
arr[c] = env->GetFloatArrayElements(cdata, 0);
input[c] = arr[c] + offset;
inbuf[c] = arr[c] + offset;
}
getStretcher(env, obj)->study(input, n, final);
getStretcher(env, obj)->study(inbuf, n, final);
for (int c = 0; c < channels; ++c) {
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(data, c);
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(input, c);
env->ReleaseFloatArrayElements(cdata, arr[c], 0);
}
deallocate(inbuf);
deallocate(arr);
}
JNIEXPORT void JNICALL
Java_com_breakfastquay_rubberband_RubberBandStretcher_process(JNIEnv *env, jobject obj, jobjectArray data, jint offset, jint n, jboolean final)
Java_com_breakfastquay_rubberband_RubberBandStretcher_process(JNIEnv *env, jobject obj, jobjectArray input, jint offset, jint n, jboolean final)
{
int channels = env->GetArrayLength(data);
int channels = env->GetArrayLength(input);
float **arr = allocate<float *>(channels);
float **input = allocate<float *>(channels);
float **inbuf = allocate<float *>(channels);
for (int c = 0; c < channels; ++c) {
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(data, c);
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(input, c);
arr[c] = env->GetFloatArrayElements(cdata, 0);
input[c] = arr[c] + offset;
inbuf[c] = arr[c] + offset;
}
getStretcher(env, obj)->process(input, n, final);
getStretcher(env, obj)->process(inbuf, n, final);
for (int c = 0; c < channels; ++c) {
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(data, c);
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(input, c);
env->ReleaseFloatArrayElements(cdata, arr[c], 0);
}
deallocate(input);
deallocate(inbuf);
deallocate(arr);
}
@@ -456,3 +540,111 @@ Java_com_breakfastquay_rubberband_RubberBandStretcher_retrieve(JNIEnv *env, jobj
return retrieved;
}
RubberBandLiveShifter *
getLiveShifter(JNIEnv *env, jobject obj)
{
jclass c = env->GetObjectClass(obj);
jfieldID fid = env->GetFieldID(c, "handle", "J");
jlong handle = env->GetLongField(obj, fid);
return (RubberBandLiveShifter *)handle;
}
void
setLiveShifter(JNIEnv *env, jobject obj, RubberBandLiveShifter *stretcher)
{
jclass c = env->GetObjectClass(obj);
jfieldID fid = env->GetFieldID(c, "handle", "J");
jlong handle = (jlong)stretcher;
env->SetLongField(obj, fid, handle);
}
JNIEXPORT void JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_initialise(JNIEnv *env, jobject obj, jint sampleRate, jint channels, jint options)
{
setLiveShifter(env, obj, new RubberBandLiveShifter
(sampleRate, channels, options));
}
JNIEXPORT void JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_dispose(JNIEnv *env, jobject obj)
{
delete getLiveShifter(env, obj);
setLiveShifter(env, obj, 0);
}
JNIEXPORT void JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_reset(JNIEnv *env, jobject obj)
{
getLiveShifter(env, obj)->reset();
}
JNIEXPORT void JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_setPitchScale(JNIEnv *env, jobject obj, jdouble scale)
{
getLiveShifter(env, obj)->setPitchScale(scale);
}
JNIEXPORT jint JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_getChannelCount(JNIEnv *env, jobject obj)
{
return getLiveShifter(env, obj)->getChannelCount();
}
JNIEXPORT jdouble JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_getPitchScale(JNIEnv *env, jobject obj)
{
return getLiveShifter(env, obj)->getPitchScale();
}
JNIEXPORT jint JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_getStartDelay(JNIEnv *env, jobject obj)
{
return getLiveShifter(env, obj)->getStartDelay();
}
JNIEXPORT void JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_setFormantOption(JNIEnv *env, jobject obj, jint options)
{
getLiveShifter(env, obj)->setFormantOption(options);
}
JNIEXPORT jint JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_getBlockSize(JNIEnv *env, jobject obj)
{
return getLiveShifter(env, obj)->getBlockSize();
}
JNIEXPORT void JNICALL
Java_com_breakfastquay_rubberband_RubberBandLiveShifter_shift(JNIEnv *env, jobject obj, jobjectArray input, jint inOffset, jobjectArray output, jint outOffset)
{
int channels = env->GetArrayLength(input);
float **inarr = allocate<float *>(channels);
float **inbuf = allocate<float *>(channels);
float **outarr = allocate<float *>(channels);
float **outbuf = allocate<float *>(channels);
for (int c = 0; c < channels; ++c) {
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(input, c);
inarr[c] = env->GetFloatArrayElements(cdata, 0);
inbuf[c] = inarr[c] + inOffset;
cdata = (jfloatArray)env->GetObjectArrayElement(output, c);
outarr[c] = env->GetFloatArrayElements(cdata, 0);
outbuf[c] = outarr[c] + outOffset;
}
getLiveShifter(env, obj)->shift(inbuf, outbuf);
for (int c = 0; c < channels; ++c) {
jfloatArray cdata = (jfloatArray)env->GetObjectArrayElement(input, c);
env->ReleaseFloatArrayElements(cdata, inarr[c], 0);
cdata = (jfloatArray)env->GetObjectArrayElement(output, c);
env->ReleaseFloatArrayElements(cdata, outarr[c], 0);
}
deallocate(inbuf);
deallocate(inarr);
deallocate(outbuf);
deallocate(outarr);
}

View File

@@ -254,6 +254,11 @@ void rubberband_live_set_formant_option(RubberBandLiveState state, RubberBandOpt
state->m_s->setFormantOption(options);
}
unsigned int rubberband_live_get_block_size(RubberBandLiveState state)
{
return (unsigned int)state->m_s->getBlockSize();
}
void rubberband_live_shift(RubberBandLiveState state, const float *const *input, float *const *output)
{
state->m_s->shift(input, output);