# HG changeset patch # User Chris Cannam # Date 1727889726 -3600 # Wed Oct 02 18:22:06 2024 +0100 # Branch rblive # Node ID 2b88a8aa6359fde7d084774fc94333e568dc50e0 # Parent 8b9607db460e71dc14b577b86b43329b3cb5f875 Add live shifter to JNI diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -31,3 +31,4 @@ formant-out-*/ out*.wav packages/ +otherbuilds/docker/Dockerfile diff --git a/com/breakfastquay/rubberband/RubberBandLiveShifter.java b/com/breakfastquay/rubberband/RubberBandLiveShifter.java new file mode 100644 --- /dev/null +++ b/com/breakfastquay/rubberband/RubberBandLiveShifter.java @@ -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"); + } +}; + diff --git a/com/breakfastquay/rubberband/test/RubberBandTest.java b/com/breakfastquay/rubberband/test/RubberBandTest.java --- a/com/breakfastquay/rubberband/test/RubberBandTest.java +++ b/com/breakfastquay/rubberband/test/RubberBandTest.java @@ -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 @@ 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 @@ } 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(); + } } diff --git a/meson.build b/meson.build --- a/meson.build +++ b/meson.build @@ -63,6 +63,7 @@ java_sources = [ 'com/breakfastquay/rubberband/RubberBandStretcher.java', + 'com/breakfastquay/rubberband/RubberBandLiveShifter.java', ] java_test_sources = [ diff --git a/otherbuilds/docker/Dockerfile.in b/otherbuilds/docker/Dockerfile.in new file mode 100644 --- /dev/null +++ b/otherbuilds/docker/Dockerfile.in @@ -0,0 +1,32 @@ +FROM ubuntu:22.04 +MAINTAINER Chris Cannam +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 diff --git a/otherbuilds/docker/run.sh b/otherbuilds/docker/run.sh new file mode 100755 --- /dev/null +++ b/otherbuilds/docker/run.sh @@ -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 . + diff --git a/rubberband/RubberBandLiveShifter.h b/rubberband/RubberBandLiveShifter.h --- a/rubberband/RubberBandLiveShifter.h +++ b/rubberband/RubberBandLiveShifter.h @@ -293,6 +293,9 @@ * 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. */ diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h --- a/rubberband/rubberband-c.h +++ b/rubberband/rubberband-c.h @@ -197,7 +197,7 @@ 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); diff --git a/src/jni/RubberBandStretcherJNI.cpp b/src/jni/RubberBandStretcherJNI.cpp --- a/src/jni/RubberBandStretcherJNI.cpp +++ b/src/jni/RubberBandStretcherJNI.cpp @@ -22,6 +22,7 @@ */ #include "rubberband/RubberBandStretcher.h" +#include "rubberband/RubberBandLiveShifter.h" #include "common/Allocators.h" @@ -231,6 +232,86 @@ 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 @@ } 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(channels); - float **input = allocate(channels); + float **inbuf = allocate(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(channels); - float **input = allocate(channels); + float **inbuf = allocate(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 @@ 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(channels); + float **inbuf = allocate(channels); + float **outarr = allocate(channels); + float **outbuf = allocate(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); +} + + diff --git a/src/rubberband-c.cpp b/src/rubberband-c.cpp --- a/src/rubberband-c.cpp +++ b/src/rubberband-c.cpp @@ -254,6 +254,11 @@ 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);