Browse Source

add test for exporting a project using an imported audio file (using chromaprint to check audio similarity)

cairo_optimizations
Alexandros Theodotou 3 years ago
parent
commit
97ea798a83
Signed by: alex
GPG Key ID: 022EAE42313D70F3
  1. 10
      inc/audio/exporter.h
  2. 2
      inc/project.h
  3. 7
      meson.build
  4. 4
      src/audio/engine.c
  5. 10
      src/audio/engine_dummy.c
  6. 62
      src/audio/exporter.c
  7. 3
      src/audio/mixer.c
  8. 4
      src/project.c
  9. 4
      tests/actions/arranger_selections.c
  10. 226
      tests/audio/exporter.c
  11. 9
      tests/meson.build
  12. BIN
      tests/test.wav

10
inc/audio/exporter.h

@ -20,6 +20,8 @@ @@ -20,6 +20,8 @@
#ifndef __AUDIO_EXPORT_H__
#define __AUDIO_EXPORT_H__
#include "audio/position.h"
/**
* @addtogroup audio
*
@ -78,7 +80,11 @@ typedef struct ExportSettings @@ -78,7 +80,11 @@ typedef struct ExportSettings
Position custom_start;
Position custom_end;
/** The previous loop status (on/off). */
/**
* The previous loop status (on/off).
*
* This is set internally.
*/
int prev_loop;
/**
@ -110,7 +116,7 @@ exporter_stringize_audio_format ( @@ -110,7 +116,7 @@ exporter_stringize_audio_format (
*
* TODO move some things into functions.
*/
void
int
exporter_export (ExportSettings * info);
/**

2
inc/project.h

@ -329,6 +329,8 @@ project_load ( @@ -329,6 +329,8 @@ project_load (
* will be saved as <original filename>.bak<num>.
* @param show_notification Show a notification
* in the UI that the project was saved.
*
* @return Non-zero if error.
*/
int
project_save (

7
meson.build

@ -396,6 +396,12 @@ if not lv2_dep.found() @@ -396,6 +396,12 @@ if not lv2_dep.found()
endif
fftw3_dep = dependency('fftw3', version: '>=3.3.5')
chromaprint_dep = dependency (
'libchromaprint', required: false)
if (chromaprint_dep.found ())
cdata.set('HAVE_CHROMAPRINT', 1)
endif
# TODO add Cantarell font as dependency
zrythm_deps = [
@ -424,6 +430,7 @@ zrythm_deps = [ @@ -424,6 +430,7 @@ zrythm_deps = [
dependency('rubberband'),
cc.find_library('dl'),
lv2_dep,
chromaprint_dep,
fftw3_dep,
# these are needed for gentoo

4
src/audio/engine.c

@ -190,7 +190,7 @@ engine_init ( @@ -190,7 +190,7 @@ engine_init (
/* get audio backend */
AudioBackend ab_code =
ZRYTHM_TESTING ?
self->audio_backend :
AUDIO_BACKEND_DUMMY :
(AudioBackend)
g_settings_get_enum (
S_PREFERENCES,
@ -234,7 +234,7 @@ engine_init ( @@ -234,7 +234,7 @@ engine_init (
/* get midi backend */
MidiBackend mb_code =
ZRYTHM_TESTING ?
self->midi_backend :
MIDI_BACKEND_DUMMY :
(MidiBackend)
g_settings_get_enum (
S_PREFERENCES,

10
src/audio/engine_dummy.c

@ -62,14 +62,12 @@ engine_dummy_setup ( @@ -62,14 +62,12 @@ engine_dummy_setup (
self->block_length = 256;
self->midi_buf_size = 4096;
g_warn_if_fail (TRANSPORT &&
TRANSPORT->beats_per_bar > 1);
g_warn_if_fail (
TRANSPORT && TRANSPORT->beats_per_bar > 1);
engine_update_frames_per_tick (
self,
TRANSPORT->beats_per_bar,
TRANSPORT->bpm,
self->sample_rate);
self, TRANSPORT->beats_per_bar,
TRANSPORT->bpm, self->sample_rate);
/* create ports */
Port * monitor_out_l, * monitor_out_r;

62
src/audio/exporter.c

@ -50,7 +50,7 @@ @@ -50,7 +50,7 @@
#include <sndfile.h>
#define AMPLITUDE (1.0 * 0x7F000000)
#define AMPLITUDE (1.0 * 0x7F000000)
/**
* Returns the audio format as string.
@ -85,7 +85,7 @@ exporter_stringize_audio_format ( @@ -85,7 +85,7 @@ exporter_stringize_audio_format (
g_return_val_if_reached (NULL);
}
static void
static int
export_audio (
ExportSettings * info)
{
@ -121,7 +121,7 @@ export_audio ( @@ -121,7 +121,7 @@ export_audio (
MAIN_WINDOW, str);
g_free (format);
return;
return -1;
}
if (info->format == AUDIO_FORMAT_OGG)
@ -184,7 +184,7 @@ export_audio ( @@ -184,7 +184,7 @@ export_audio (
ui_show_error_message (
MAIN_WINDOW,
"SF INFO invalid");
return;
return - 1;
}
char * dir = io_get_dir (info->file_uri);
@ -213,32 +213,24 @@ export_audio ( @@ -213,32 +213,24 @@ export_audio (
break;
default:
g_warn_if_reached ();
return;
return - 1;
}
g_warning (
"Couldn't open SNDFILE %s:\n%s",
info->file_uri, error_str);
return;
return - 1;
}
sf_set_string (
sndfile,
SF_STR_TITLE,
PROJECT->title);
sndfile, SF_STR_TITLE, PROJECT->title);
sf_set_string (
sndfile,
SF_STR_SOFTWARE,
"Zrythm");
sndfile, SF_STR_SOFTWARE, "Zrythm");
sf_set_string (
sndfile,
SF_STR_ARTIST,
info->artist);
sndfile, SF_STR_ARTIST, info->artist);
sf_set_string (
sndfile,
SF_STR_GENRE,
info->genre);
sndfile, SF_STR_GENRE, info->genre);
Position prev_playhead_pos;
/* position to start at */
@ -301,16 +293,16 @@ export_audio ( @@ -301,16 +293,16 @@ export_audio (
&AUDIO_ENGINE->port_operation_lock);
nframes_t nframes;
g_return_if_fail (
g_return_val_if_fail (
stop_pos.frames >= 1 ||
start_pos.frames >= 0);
start_pos.frames >= 0, -1);
const unsigned long total_frames =
(unsigned long)
((stop_pos.frames - 1) -
start_pos.frames);
sf_count_t covered = 0;
float out_ptr[
AUDIO_ENGINE->nframes * EXPORT_CHANNELS];
AUDIO_ENGINE->block_length * EXPORT_CHANNELS];
do
{
/* calculate number of frames to process
@ -320,15 +312,14 @@ export_audio ( @@ -320,15 +312,14 @@ export_audio (
MIN (
(stop_pos.frames - 1) -
TRANSPORT->playhead_pos.frames,
(long) AUDIO_ENGINE->nframes);
(long) AUDIO_ENGINE->block_length);
g_return_val_if_fail (nframes > 0, -1);
/* run process code */
engine_process_prepare (
AUDIO_ENGINE,
nframes);
AUDIO_ENGINE, nframes);
router_start_cycle (
&MIXER->router, nframes,
0, PLAYHEAD);
&MIXER->router, nframes, 0, PLAYHEAD);
engine_post_process (
AUDIO_ENGINE, nframes);
@ -413,17 +404,22 @@ export_audio ( @@ -413,17 +404,22 @@ export_audio (
&prev_playhead_pos);
sf_close (sndfile);
g_message (
"successfully exported to %s", info->file_uri);
return 0;
}
static void
static int
export_midi (
ExportSettings * info)
{
MIDI_FILE *mf;
int i;
if ((mf = midiFileCreate (info->file_uri, TRUE)))
{
if ((mf = midiFileCreate (info->file_uri, TRUE)))
{
/* Write tempo information out to track 1 */
midiSongAddTempo (
mf, 1, (int) TRANSPORT->bpm);
@ -456,6 +452,8 @@ export_midi ( @@ -456,6 +452,8 @@ export_midi (
midiFileClose(mf);
}
info->progress = 1.0;
return 0;
}
/**
@ -464,16 +462,16 @@ export_midi ( @@ -464,16 +462,16 @@ export_midi (
*
* TODO move some things into functions.
*/
void
int
exporter_export (ExportSettings * info)
{
if (info->format == AUDIO_FORMAT_MIDI)
{
export_midi (info);
return export_midi (info);
}
else
{
export_audio (info);
return export_audio (info);
}
}

3
src/audio/mixer.c

@ -61,9 +61,6 @@ void @@ -61,9 +61,6 @@ void
mixer_recalc_graph (
Mixer * mixer)
{
if (ZRYTHM_TESTING)
return;
Router * router = &mixer->router;
if (!router->graph)
{

4
src/project.c

@ -668,7 +668,7 @@ load ( @@ -668,7 +668,7 @@ load (
MW_HEADER_NOTEBOOK,
PROJECT->title);
RETURN_OK;
return 0;
}
/**
@ -882,6 +882,8 @@ project_get_project_file_path ( @@ -882,6 +882,8 @@ project_get_project_file_path (
* will be saved as <original filename>.bak<num>.
* @param show_notification Show a notification
* in the UI that the project was saved.
*
* @return Non-zero if error.
*/
int
project_save (

4
tests/actions/arranger_selections.c

@ -241,8 +241,8 @@ rebootstrap_timeline () @@ -241,8 +241,8 @@ rebootstrap_timeline ()
&p1, track->pos, AUDIO_REGION_LANE, 0);
AudioClip * clip =
audio_region_get_clip (r);
g_assert_cmpint (clip->num_frames, >, 290000);
g_assert_cmpint (clip->num_frames, <, 294000);
g_assert_cmpint (clip->num_frames, >, 151000);
g_assert_cmpint (clip->num_frames, <, 152000);
track_add_region (
track, r, NULL, AUDIO_REGION_LANE, 1, 0);
region_set_name (r, AUDIO_REGION_NAME, 0);

226
tests/audio/exporter.c

@ -0,0 +1,226 @@ @@ -0,0 +1,226 @@
/*
* Copyright (C) 2020 Alexandros Theodotou <alex at zrythm dot org>
*
* This file is part of Zrythm
*
* Zrythm is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zrythm 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
*/
#include "actions/create_tracks_action.h"
#include "audio/encoder.h"
#include "audio/exporter.h"
#include "audio/supported_file.h"
#include "helpers/zrythm.h"
#include "project.h"
#include "utils/math.h"
#include "zrythm.h"
#include <glib.h>
#include <chromaprint.h>
#include <sndfile.h>
/**
* Chroma fingerprint info for a specific file.
*/
typedef struct ChromaprintFingerprint
{
uint32_t * fp;
int size;
char * compressed_str;
} ChromaprintFingerprint;
static void
chromaprint_fingerprint_free (
ChromaprintFingerprint * self)
{
chromaprint_dealloc (self->fp);
chromaprint_dealloc (self->compressed_str);
}
static long
get_num_frames (
const char * file)
{
SF_INFO sfinfo;
memset (&sfinfo, 0, sizeof (sfinfo));
sfinfo.format =
sfinfo.format | SF_FORMAT_PCM_16;
SNDFILE * sndfile =
sf_open (file, SFM_READ, &sfinfo);
g_assert_nonnull (sndfile);
g_assert_cmpint (sfinfo.frames, >, 0);
long frames = sfinfo.frames;
int ret = sf_close (sndfile);
g_assert_cmpint (ret, ==, 0);
return frames;
}
static ChromaprintFingerprint *
get_fingerprint (
const char * file1,
long max_frames)
{
int ret;
SF_INFO sfinfo;
memset (&sfinfo, 0, sizeof (sfinfo));
sfinfo.format =
sfinfo.format | SF_FORMAT_PCM_16;
SNDFILE * sndfile =
sf_open (file1, SFM_READ, &sfinfo);
g_assert_nonnull (sndfile);
g_assert_cmpint (sfinfo.frames, >, 0);
ChromaprintContext * ctx =
chromaprint_new (CHROMAPRINT_ALGORITHM_DEFAULT);
g_assert_nonnull (ctx);
ret = chromaprint_start (
ctx, sfinfo.samplerate, sfinfo.channels);
g_assert_cmpint (ret, ==, 1);
int buf_size = sfinfo.frames * sfinfo.channels;
short data[buf_size];
sf_count_t frames_read =
sf_readf_short (sndfile, data, sfinfo.frames);
g_assert_cmpint (frames_read, ==, sfinfo.frames);
g_message (
"read %ld frames for %s", frames_read, file1);
ret = chromaprint_feed (ctx, data, buf_size);
g_assert_cmpint (ret, ==, 1);
ret = chromaprint_finish (ctx);
g_assert_cmpint (ret, ==, 1);
ChromaprintFingerprint * fp =
calloc (1, sizeof (ChromaprintFingerprint));
ret = chromaprint_get_fingerprint (
ctx, &fp->compressed_str);
g_assert_cmpint (ret, ==, 1);
ret = chromaprint_get_raw_fingerprint (
ctx, &fp->fp, &fp->size);
g_assert_cmpint (ret, ==, 1);
g_message (
"fingerprint %s [%d]",
fp->compressed_str, fp->size);
chromaprint_free (ctx);
ret = sf_close (sndfile);
g_assert_cmpint (ret, ==, 0);
return fp;
}
/**
* @param perc Minimum percentage of equal
* fingerprints required.
*/
static void
check_fingerprint_similarity (
const char * file1,
const char * file2,
int perc)
{
const long max_frames =
MIN (
get_num_frames (file1),
get_num_frames (file2));
ChromaprintFingerprint * fp1 =
get_fingerprint (file1, max_frames);
g_assert_cmpint (fp1->size, ==, 6);
ChromaprintFingerprint * fp2 =
get_fingerprint (file2, max_frames);
int min = MIN (fp1->size, fp2->size);
g_assert_cmpint (min, !=, 0);
int rate = 0;
for (int i = 0; i < min; i++)
{
if (fp1->fp[i] == fp2->fp[i])
rate++;
}
double rated = (double) rate / (double) min;
int rate_perc =
math_round_double_to_int (rated * 100.0);
g_message (
"%d out of %d (%d%%)", rate, min, rate_perc);
g_assert_cmpint (rate_perc, >=, perc);
chromaprint_fingerprint_free (fp1);
chromaprint_fingerprint_free (fp2);
}
static void
test_export_wav ()
{
int ret;
char * filepath =
g_build_filename (
TESTS_SRCDIR, "test.wav", NULL);
SupportedFile * file =
supported_file_new_from_path (filepath);
UndoableAction * action =
create_tracks_action_new (
TRACK_TYPE_AUDIO, NULL, file,
TRACKLIST->num_tracks, 1);
undo_manager_perform (UNDO_MANAGER, action);
char * tmp_dir =
g_dir_make_tmp ("test_wav_prj_XXXXXX", NULL);
ret = project_save (PROJECT, tmp_dir, 0, 0);
g_free (tmp_dir);
g_assert_cmpint (ret, ==, 0);
ExportSettings settings;
settings.format = AUDIO_FORMAT_WAV;
settings.artist = g_strdup ("Test Artist");
settings.genre = g_strdup ("Test Genre");
settings.depth = BIT_DEPTH_16;
settings.time_range = TIME_RANGE_LOOP;
char * exports_dir =
project_get_exports_dir (PROJECT);
settings.file_uri =
g_build_filename (
exports_dir, "test_wav.wav", NULL);
ret = exporter_export (&settings);
g_assert_cmpint (ret, ==, 0);
check_fingerprint_similarity (
filepath, settings.file_uri, 100);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
test_helper_zrythm_init ();
#define TEST_PREFIX "/audio/exporter/"
g_test_add_func (
TEST_PREFIX "test export wav",
(GTestFunc) test_export_wav);
return g_test_run ();
}

9
tests/meson.build

@ -44,7 +44,8 @@ if get_option ('enable_tests') @@ -44,7 +44,8 @@ if get_option ('enable_tests')
'-fPIC',
]
# path, is parallel
# 0: path
# 1: is parallel
tests = [
['actions/arranger_selections', true],
['audio/automation_track', true],
@ -70,6 +71,12 @@ if get_option ('enable_tests') @@ -70,6 +71,12 @@ if get_option ('enable_tests')
]
endif
if (chromaprint_dep.found ())
tests += [
['audio/exporter', true],
]
endif
foreach _test : tests
test_name = _test[0]
parallel = _test[1]

BIN
tests/test.wav

Binary file not shown.
Loading…
Cancel
Save