mirror of
https://github.com/RPCS3/soundtouch.git
synced 2024-09-19 16:01:37 +02:00
322 lines
9.9 KiB
C++
322 lines
9.9 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// SoundStretch main routine.
|
|
///
|
|
/// Author : Copyright (c) Olli Parviainen
|
|
/// Author e-mail : oparviai 'at' iki.fi
|
|
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// License :
|
|
//
|
|
// SoundTouch audio processing library
|
|
// Copyright (c) Olli Parviainen
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
// This library 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
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <cstdio>
|
|
#include <ctime>
|
|
#include "RunParameters.h"
|
|
#include "WavFile.h"
|
|
#include "SoundTouch.h"
|
|
#include "BPMDetect.h"
|
|
|
|
using namespace soundtouch;
|
|
using namespace std;
|
|
|
|
namespace soundstretch
|
|
{
|
|
|
|
// Processing chunk size (size chosen to be divisible by 2, 4, 6, 8, 10, 12, 14, 16 channels ...)
|
|
#define BUFF_SIZE 6720
|
|
|
|
#if _WIN32
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
|
|
// Macro for Win32 standard input/output stream support: Sets a file stream into binary mode
|
|
#define SET_STREAM_TO_BIN_MODE(f) (_setmode(_fileno(f), _O_BINARY))
|
|
#else
|
|
// Not needed for GNU environment...
|
|
#define SET_STREAM_TO_BIN_MODE(f) {}
|
|
#endif
|
|
|
|
|
|
static const char _helloText[] =
|
|
"\n"
|
|
" SoundStretch v%s - Copyright (c) Olli Parviainen\n"
|
|
"=========================================================\n"
|
|
"author e-mail: <oparviai"
|
|
"@"
|
|
"iki.fi> - WWW: http://www.surina.net/soundtouch\n"
|
|
"\n"
|
|
"This program is subject to (L)GPL license. Run \"soundstretch -license\" for\n"
|
|
"more information.\n"
|
|
"\n";
|
|
|
|
static void openFiles(unique_ptr<WavInFile>& inFile, unique_ptr<WavOutFile>& outFile, const RunParameters& params)
|
|
{
|
|
if (params.inFileName == STRING_CONST("stdin"))
|
|
{
|
|
// used 'stdin' as input file
|
|
SET_STREAM_TO_BIN_MODE(stdin);
|
|
inFile = make_unique<WavInFile>(stdin);
|
|
}
|
|
else
|
|
{
|
|
// open input file...
|
|
inFile = make_unique<WavInFile>(params.inFileName.c_str());
|
|
}
|
|
|
|
// ... open output file with same sound parameters
|
|
const int bits = (int)inFile->getNumBits();
|
|
const int samplerate = (int)inFile->getSampleRate();
|
|
const int channels = (int)inFile->getNumChannels();
|
|
|
|
if (!params.outFileName.empty())
|
|
{
|
|
if (params.outFileName == STRING_CONST("stdout"))
|
|
{
|
|
SET_STREAM_TO_BIN_MODE(stdout);
|
|
outFile = make_unique<WavOutFile>(stdout, samplerate, bits, channels);
|
|
}
|
|
else
|
|
{
|
|
outFile = make_unique<WavOutFile>(params.outFileName.c_str(), samplerate, bits, channels);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Sets the 'SoundTouch' object up according to input file sound format &
|
|
// command line parameters
|
|
static void setup(SoundTouch& soundTouch, const WavInFile& inFile, const RunParameters& params)
|
|
{
|
|
const int sampleRate = (int)inFile.getSampleRate();
|
|
const int channels = (int)inFile.getNumChannels();
|
|
soundTouch.setSampleRate(sampleRate);
|
|
soundTouch.setChannels(channels);
|
|
|
|
soundTouch.setTempoChange(params.tempoDelta);
|
|
soundTouch.setPitchSemiTones(params.pitchDelta);
|
|
soundTouch.setRateChange(params.rateDelta);
|
|
|
|
soundTouch.setSetting(SETTING_USE_QUICKSEEK, params.quick);
|
|
soundTouch.setSetting(SETTING_USE_AA_FILTER, !(params.noAntiAlias));
|
|
|
|
if (params.speech)
|
|
{
|
|
// use settings for speech processing
|
|
soundTouch.setSetting(SETTING_SEQUENCE_MS, 40);
|
|
soundTouch.setSetting(SETTING_SEEKWINDOW_MS, 15);
|
|
soundTouch.setSetting(SETTING_OVERLAP_MS, 8);
|
|
fprintf(stderr, "Tune processing parameters for speech processing.\n");
|
|
}
|
|
|
|
// print processing information
|
|
if (!params.outFileName.empty())
|
|
{
|
|
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
|
|
fprintf(stderr, "Uses 16bit integer sample type in processing.\n\n");
|
|
#else
|
|
#ifndef SOUNDTOUCH_FLOAT_SAMPLES
|
|
#error "Sampletype not defined"
|
|
#endif
|
|
fprintf(stderr, "Uses 32bit floating point sample type in processing.\n\n");
|
|
#endif
|
|
// print processing information only if outFileName given i.e. some processing will happen
|
|
fprintf(stderr, "Processing the file with the following changes:\n");
|
|
fprintf(stderr, " tempo change = %+g %%\n", params.tempoDelta);
|
|
fprintf(stderr, " pitch change = %+g semitones\n", params.pitchDelta);
|
|
fprintf(stderr, " rate change = %+g %%\n\n", params.rateDelta);
|
|
fprintf(stderr, "Working...");
|
|
}
|
|
else
|
|
{
|
|
// outFileName not given
|
|
fprintf(stderr, "Warning: output file name missing, won't output anything.\n\n");
|
|
}
|
|
|
|
fflush(stderr);
|
|
}
|
|
|
|
|
|
// Processes the sound
|
|
static void process(SoundTouch& soundTouch, WavInFile& inFile, WavOutFile& outFile)
|
|
{
|
|
SAMPLETYPE sampleBuffer[BUFF_SIZE];
|
|
int nSamples;
|
|
|
|
const int nChannels = (int)inFile.getNumChannels();
|
|
assert(nChannels > 0);
|
|
const int buffSizeSamples = BUFF_SIZE / nChannels;
|
|
|
|
// Process samples read from the input file
|
|
while (inFile.eof() == 0)
|
|
{
|
|
// Read a chunk of samples from the input file
|
|
const int num = inFile.read(sampleBuffer, BUFF_SIZE);
|
|
int nSamples = num / (int)inFile.getNumChannels();
|
|
|
|
// Feed the samples into SoundTouch processor
|
|
soundTouch.putSamples(sampleBuffer, nSamples);
|
|
|
|
// Read ready samples from SoundTouch processor & write them output file.
|
|
// NOTES:
|
|
// - 'receiveSamples' doesn't necessarily return any samples at all
|
|
// during some rounds!
|
|
// - On the other hand, during some round 'receiveSamples' may have more
|
|
// ready samples than would fit into 'sampleBuffer', and for this reason
|
|
// the 'receiveSamples' call is iterated for as many times as it
|
|
// outputs samples.
|
|
do
|
|
{
|
|
nSamples = soundTouch.receiveSamples(sampleBuffer, buffSizeSamples);
|
|
outFile.write(sampleBuffer, nSamples * nChannels);
|
|
} while (nSamples != 0);
|
|
}
|
|
|
|
// Now the input file is processed, yet 'flush' few last samples that are
|
|
// hiding in the SoundTouch's internal processing pipeline.
|
|
soundTouch.flush();
|
|
do
|
|
{
|
|
nSamples = soundTouch.receiveSamples(sampleBuffer, buffSizeSamples);
|
|
outFile.write(sampleBuffer, nSamples * nChannels);
|
|
} while (nSamples != 0);
|
|
}
|
|
|
|
|
|
// Detect BPM rate of inFile and adjust tempo setting accordingly if necessary
|
|
static void detectBPM(WavInFile& inFile, RunParameters& params)
|
|
{
|
|
BPMDetect bpm(inFile.getNumChannels(), inFile.getSampleRate());
|
|
SAMPLETYPE sampleBuffer[BUFF_SIZE];
|
|
|
|
// detect bpm rate
|
|
fprintf(stderr, "Detecting BPM rate...");
|
|
fflush(stderr);
|
|
|
|
const int nChannels = (int)inFile.getNumChannels();
|
|
int readSize = BUFF_SIZE - BUFF_SIZE % nChannels; // round read size down to multiple of num.channels
|
|
|
|
// Process the 'inFile' in small blocks, repeat until whole file has
|
|
// been processed
|
|
while (inFile.eof() == 0)
|
|
{
|
|
// Read sample data from input file
|
|
const int num = inFile.read(sampleBuffer, readSize);
|
|
|
|
// Enter the new samples to the bpm analyzer class
|
|
const int samples = num / nChannels;
|
|
bpm.inputSamples(sampleBuffer, samples);
|
|
}
|
|
|
|
// Now the whole song data has been analyzed. Read the resulting bpm.
|
|
const float bpmValue = bpm.getBpm();
|
|
fprintf(stderr, "Done!\n");
|
|
|
|
// rewind the file after bpm detection
|
|
inFile.rewind();
|
|
|
|
if (bpmValue > 0)
|
|
{
|
|
fprintf(stderr, "Detected BPM rate %.1f\n\n", bpmValue);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "Couldn't detect BPM rate.\n\n");
|
|
return;
|
|
}
|
|
|
|
if (params.goalBPM > 0)
|
|
{
|
|
// adjust tempo to given bpm
|
|
params.tempoDelta = (params.goalBPM / bpmValue - 1.0f) * 100.0f;
|
|
fprintf(stderr, "The file will be converted to %.1f BPM\n\n", params.goalBPM);
|
|
}
|
|
}
|
|
|
|
void printHelloText()
|
|
{
|
|
SoundTouch soundTouch;
|
|
fprintf(stderr, _helloText, soundTouch.getVersionString());
|
|
}
|
|
|
|
void ss_main(RunParameters& params)
|
|
{
|
|
unique_ptr<WavInFile> inFile;
|
|
unique_ptr<WavOutFile> outFile;
|
|
SoundTouch soundTouch;
|
|
|
|
// Open input & output files
|
|
openFiles(inFile, outFile, params);
|
|
|
|
if (params.detectBPM == true)
|
|
{
|
|
// detect sound BPM (and adjust processing parameters
|
|
// accordingly if necessary)
|
|
detectBPM(*inFile, params);
|
|
}
|
|
|
|
// Setup the 'SoundTouch' object for processing the sound
|
|
setup(soundTouch, *inFile, params);
|
|
|
|
// clock_t cs = clock(); // for benchmarking processing duration
|
|
// Process the sound
|
|
if (inFile && outFile)
|
|
{
|
|
process(soundTouch, *inFile, *outFile);
|
|
}
|
|
// clock_t ce = clock(); // for benchmarking processing duration
|
|
// printf("duration: %lf\n", (double)(ce-cs)/CLOCKS_PER_SEC);
|
|
|
|
fprintf(stderr, "Done!\n");
|
|
}
|
|
|
|
}
|
|
|
|
#if _WIN32
|
|
int wmain(int argc, const wchar_t* args[])
|
|
#else
|
|
int main(int argc, const char* args[])
|
|
#endif
|
|
{
|
|
try
|
|
{
|
|
soundstretch::printHelloText();
|
|
soundstretch::RunParameters params(argc, args);
|
|
soundstretch::ss_main(params);
|
|
}
|
|
catch (const runtime_error& e)
|
|
{
|
|
fprintf(stderr, "%s\n", e.what());
|
|
return -1;
|
|
}
|
|
catch (const string& e)
|
|
{
|
|
fprintf(stderr, "%s\n", e.c_str());
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|