Skip to content

Commit

Permalink
Add rubberband module and rbpitch filter.
Browse files Browse the repository at this point in the history
  • Loading branch information
bmatherly committed Jan 26, 2020
1 parent 4e8b419 commit ccd8cb5
Show file tree
Hide file tree
Showing 6 changed files with 378 additions and 0 deletions.
42 changes: 42 additions & 0 deletions src/modules/rubberband/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
CFLAGS += -I../..

LDFLAGS := -L../../framework -lmlt $(LDFLAGS)

include ../../../config.mak

TARGET = ../libmltrubberband$(LIBSUF)

OBJS = factory.o

CPPOBJS = filter_rbpitch.o

CFLAGS += $(shell pkg-config --cflags rubberband)

CXXFLAGS += -Wno-deprecated $(CFLAGS)

LDFLAGS += $(shell pkg-config --libs rubberband)

SRCS := $(OBJS:.o=.c) $(CPPOBJS:.o=.cpp)

all: $(TARGET)

$(TARGET): $(OBJS) $(CPPOBJS)
$(CXX) $(SHFLAGS) -o $@ $(OBJS) $(CPPOBJS) $(LDFLAGS)

depend: $(SRCS)
$(CXX) -MM $(CXXFLAGS) $^ 1>.depend

distclean: clean
rm -f .depend config.h config.mak

clean:
rm -f $(OBJS) $(TARGET) $(CPPOBJS)

install: all
install -m 755 $(TARGET) "$(DESTDIR)$(moduledir)"
install -d "$(DESTDIR)$(mltdatadir)/rubberband"
install -m 644 *.yml "$(DESTDIR)$(mltdatadir)/rubberband"

ifneq ($(wildcard .depend),)
include .depend
endif
16 changes: 16 additions & 0 deletions src/modules/rubberband/configure
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh

if [ "$help" != "1" ]
then

pkg-config rubberband 2> /dev/null
disable_rubberband=$?

if [ "$disable_rubberband" != "0" ]
then
echo "- rubberband not found: disabling"
touch ../disable-rubberband
fi
exit 0
fi

37 changes: 37 additions & 0 deletions src/modules/rubberband/factory.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* factory.c -- the factory method interfaces
* Copyright (C) 2020 Meltytech, LLC
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#include <string.h>
#include <limits.h>
#include <framework/mlt.h>

extern mlt_filter filter_rbpitch_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );

static mlt_properties metadata( mlt_service_type type, const char *id, void *data )
{
char file[ PATH_MAX ];
snprintf( file, PATH_MAX, "%s/rubberband/%s", mlt_environment( "MLT_DATA" ), (char*) data );
return mlt_properties_parse_yaml( file );
}

MLT_REPOSITORY
{
MLT_REGISTER( filter_type, "rbpitch", filter_rbpitch_init );
MLT_REGISTER_METADATA( filter_type, "rbpitch", metadata, "filter_rbpitch.yml" );
}
227 changes: 227 additions & 0 deletions src/modules/rubberband/filter_rbpitch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
* filter_rbpitch.c -- adjust audio pitch
* Copyright (C) 2020 Meltytech, LLC
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#include <framework/mlt.h>
#include <framework/mlt_log.h>

#include <rubberband/RubberBandStretcher.h>

#include <algorithm>
#include <cstring>
#include <math.h>

using namespace RubberBand;

static const size_t MAX_CHANNELS = 10;

static void collapse_channels( int channels, int allocated_samples, int used_samples, float* buffer )
{
if ( allocated_samples != used_samples && used_samples != 0 )
{
for ( int p = 0; p < channels; p++ )
{
float* src = buffer + ( p * allocated_samples );
float* dst = buffer + ( p * used_samples );
memmove( dst, src, used_samples * sizeof(float) );
}
}
}

static int rbpitch_get_audio( mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples )
{
mlt_filter filter = static_cast<mlt_filter>(mlt_frame_pop_audio( frame ));
if ( *channels > (int)MAX_CHANNELS )
{
mlt_log_error( MLT_FILTER_SERVICE(filter), "Too many channels requested: %d > %d\n", *channels, (int)MAX_CHANNELS );
return mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples );
}

int requested_samples = *samples;
mlt_properties unique_properties = mlt_frame_get_unique_properties( frame, MLT_FILTER_SERVICE(filter) );
if ( !unique_properties )
{
mlt_log_error( MLT_FILTER_SERVICE(filter), "Missing unique_properites\n" );
return mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples );
}

// Get the producer's audio
*format = mlt_audio_float;
int error = mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples );
if ( error ) return error;

bool last_frame = false;
if ( mlt_filter_get_position( filter, frame ) + 1 == mlt_filter_get_length2( filter, frame ) )
{
// This is the last mlt frame. Drain the rest of the stretcher
last_frame = true;
}

// Protect the RubberBandStretcher instance.
mlt_service_lock( MLT_FILTER_SERVICE(filter) );

// Configure the stretcher.
double pitchscale = mlt_properties_get_double( unique_properties, "pitchscale" );
RubberBandStretcher* s = static_cast<RubberBandStretcher*>(filter->child);
if ( !s || (int)s->getChannelCount() != *channels || (int)s->getSampleRate() != *frequency || s->available() == -1 )
{
mlt_log_debug( MLT_FILTER_SERVICE(filter), "Create a new stretcher\n" );
delete s;
// Create a rubberband instance
RubberBandStretcher::Options options = RubberBandStretcher::OptionProcessRealTime |
RubberBandStretcher::OptionPitchHighConsistency;
s = new RubberBandStretcher(*frequency, *channels, options, 1.0, pitchscale);
filter->child = s;
}
s->setPitchScale(pitchscale);

// Calculate the buffer size.
int in_samples = *samples;
int consumed_samples = 0;
int out_samples = 0;
int alloc_samples = in_samples + s->getLatency();
if ( last_frame )
{
// Allocate enough space for the rest of the samples in the stretcher.
alloc_samples += s->getLatency() * 2;
}
int size = mlt_audio_format_size( *format, alloc_samples, *channels );
float* out_buffer = (float*)mlt_pool_alloc( size );

// Process all input samples
while ( true )
{
// Send more samples to the stretcher
int required_samples = (int)s->getSamplesRequired();
int remaining_in_samples = in_samples - consumed_samples;
int process_samples = std::min( remaining_in_samples, required_samples );
if ( process_samples > 0 )
{
float* in_planes[MAX_CHANNELS];
for ( int i = 0; i < *channels; i++ )
{
in_planes[i] = ((float*)*buffer) + (in_samples * i) + consumed_samples;
}
if ( last_frame && process_samples == remaining_in_samples )
{
// This will be the last call to process.
s->process( in_planes, process_samples, true );
mlt_log_debug( MLT_FILTER_SERVICE(filter), "Last call to Process()\n" );
}
else
{
s->process( in_planes, process_samples, false );
}
consumed_samples += process_samples;
}

// Receive samples from to the stretcher
int retrieve_samples = std::min( alloc_samples - out_samples, s->available() );
if ( retrieve_samples > 0 )
{
float* out_planes[MAX_CHANNELS];
for ( int i = 0; i < *channels; i++ )
{
out_planes[i] = out_buffer + (alloc_samples * i) + out_samples;
}
retrieve_samples = (int)s->retrieve( out_planes, retrieve_samples );
out_samples += retrieve_samples;
}

mlt_log_debug( MLT_FILTER_SERVICE(filter), "Process: %d\t Retrieve: %d\n", process_samples, retrieve_samples );

if ( process_samples <= 0 && retrieve_samples <= 0 )
{
// There is nothing more to do;
break;
}
}

// Save the processed samples.
collapse_channels( *channels, alloc_samples, out_samples, out_buffer );
mlt_frame_set_audio( frame, static_cast<void*>(out_buffer), *format, size, mlt_pool_release );
*buffer = static_cast<void*>(out_buffer);
*samples = out_samples;

// Report the latency.
double latency = (double)s->getLatency() * 1000.0 / (double)*frequency;
mlt_properties_set_double( MLT_FILTER_PROPERTIES( filter ), "latency", latency );

mlt_service_unlock( MLT_FILTER_SERVICE(filter) );

mlt_log_debug( MLT_FILTER_SERVICE(filter), "Requested: %d\tReceived: %d\tSent: %d\tLatency: %f\n", requested_samples, in_samples, out_samples, latency );
return error;
}

static mlt_frame filter_process( mlt_filter filter, mlt_frame frame )
{
mlt_properties filter_properties = MLT_FILTER_PROPERTIES( filter );
mlt_position position = mlt_filter_get_position( filter, frame );
mlt_position length = mlt_filter_get_length2( filter, frame );

// Determine the pitchscale
double pitchscale = 1.0;
if ( mlt_properties_get( filter_properties, "pitchscale" ) != NULL )
{
pitchscale = mlt_properties_anim_get_double( filter_properties, "pitchscale", position, length );
}
else
{
double octaveshift = mlt_properties_anim_get_double( filter_properties, "octaveshift", position, length );
pitchscale = pow(2, octaveshift);
}
if ( pitchscale <= 0.0 || /*check for nan:*/pitchscale != pitchscale )
{
pitchscale = 1.0;
}

// Save the pitchscale on the frame to be used in rbpitch_get_audio
mlt_properties unique_properties = mlt_frame_unique_properties( frame, MLT_FILTER_SERVICE(filter) );
mlt_properties_set_double( unique_properties, "pitchscale", pitchscale );

mlt_frame_push_audio( frame, (void*)filter );
mlt_frame_push_audio( frame, (void*)rbpitch_get_audio );

return frame;
}

static void close_filter( mlt_filter filter )
{
RubberBandStretcher* s = static_cast<RubberBandStretcher*>(filter->child);
if ( s )
{
delete s;
filter->child = NULL;
}
}

extern "C" {

mlt_filter filter_rbpitch_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
{
mlt_filter filter = mlt_filter_new();
if ( filter != NULL )
{
filter->process = filter_process;
filter->close = close_filter;
filter->child = NULL;
}
return filter;
}

}
56 changes: 56 additions & 0 deletions src/modules/rubberband/filter_rbpitch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
schema_version: 0.3
type: filter
identifier: rbpitch
title: Rubberband Pitch
version: 1
copyright: Meltytech, LLC
creator: Brian Matherly
license: GPLv2
language: en
tags:
- Audio
description: Adjust the audio pitch using the Rubberband library.
parameters:
- identifier: octaveshift
title: Octave Shift
type: float
description: >
The octave shift. This is the octave shift of the source frequency. For
example, a shift of +1 would double the frequency; -1 would halve the
frequency and 0 would leave the pitch unaffected. To put this in frequency
terms, a frequency shift f (where f greater than one for upwards shift and
less than one for downwards) is: o = log(f) / log(2).
Ignored if pitchscale is set.
readonly: no
mutable: yes
default: 0.0
minimum: -3.3
maximum: 3.3
unit: octaves

- identifier: pitchscale
title: Pitch Scale
type: float
description: >
The pitch scaling ratio. This is the ratio of target frequency to source
frequency. For example, a ratio of 2.0 would shift up by one octave; 0.5
down by one octave; or 1.0 leave the pitch unaffected. To put this in
musical terms, a pitch scaling ratio corresponding to a shift of o
octaves (where o is positive for an upwards shift and
negative for downwards) is: f = pow(2.0, o).
Overrides octaveshift.
readonly: no
mutable: yes
default: 1.0
minimum: 0.1
maximum: 10

- identifier: latency
title: Latency
type: float
description: >
The amount of delay for each sample from the input to the output.
readonly: yes
unit: ms
Empty file added src/modules/rubberband/gpl
Empty file.

0 comments on commit ccd8cb5

Please sign in to comment.