//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Oct 24 00:44:06 2002
// Last Modified: Thu Oct 24 09:14:26 PDT 2002
// Filename: ...sig/doc/examples/sig/sigfile/vogran/vogran.cpp
// Syntax: C++; sig
//
// Description: Granulate a soundfile by layers of grain voices.
//
// Input parameters:
// * number of voices (envelope)
// * grain size (envelope) milliseconds [default 50]
// * grain size variation (envelope) milliseconds [default 0]
// * transition width in milliseconds (envelope) [default 5]
// * start time in sound file in seconds [default 0]
// * duration of sound in input file in seconds [default -1 = end of sound]
// * duration of output sound in seconds [default = -1 = duration of input]
// * gap time in milliseconds (envelope) [default 10]
// * gap variation in milliseconds (envelope) [default 0]
// * time variation for synchrony of voices/grains (envelope) in ms [default 0]
//
// * optional normalization level
// * optional output amplitude envelope
// * density check.
//
// Note: Parameter memory is a hog.
//
#include "sigAudio.h"
#include "SoundFileWrite.h"
#include "SoundFileRead.h"
#include <stdlib.h>
#ifndef OLDCPP
#include <iostream>
using namespace std;
#else
#include <iostream.h>
#endif
class voiceItem {
public:
int voice; // voice to which this item belongs
int grainsize; // size of grain in samples
int transition; // size of transition in samples
int start; // start time of opening transition
int offset; // time offset from input in samples
void clear(void) {
voice = grainsize = transition = start = offset = 0;
}
void print(void) {
cout << "v: " << voice << "\t";
cout << "grnsz: " << grainsize << "\t";
cout << "trans: " << transition << "\t";
cout << "strt: " << start << "\t";
cout << "ofst: " << offset << "\n";
}
};
// function declarations:
void checkOptions(Options& opts);
void example(void);
void usage(const char* command);
void fillInputBuffer(CircularBuffer<double>& inputsound, const char* infile,
double start, double dur);
void doGranulations(CircularBuffer<double>& inputdata,
Array<double>& outputdata);
int randomvar(int base, int var);
void addVoiceGrains(Array<voiceItem>& grains, int voice,
Array<int>& voiceenv, Array<int>& grainsize,
Array<int>& grainvarsize, Array<int>& gapsize,
Array<int>& gapvarsize, Array<int>& transitions);
void insertGrain(Array<double>& outputdata,
CircularBuffer<double>& inputdata, voiceItem& grain);
void makeHalfWindow(Array<double>& window, int direction);
// input parameters:
int densityQ = 0; // used with --density
int verboseQ = 0; // used with --verbose option
double starttime = 0; // used with -s option
double duration = -1; // used with -d option
double outputdur = -1; // used with --od option
const char* voiceenv = "1"; // used with -v option
const char* grainenv = "0 50 1 50"; // used with -g option
const char* grainvarenv= "0 0 1 0"; // used with --gv option
const char* gapenv = "0 50 1 50"; // used with --gap option
const char* gapvarenv = "0 0 1 0"; // used with --gapv option
const char* transenv = "0 5 1 5"; // used with -t option
///////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]) {
Options options(argc, argv);
checkOptions(options);
CircularBuffer<double> inputdata;
fillInputBuffer(inputdata, options.getArg(1), starttime, duration);
Array<double> outputdata;
int outputsize = 0;
if (outputdur <= 0) {
outputsize = inputdata.getSize();
} else {
outputsize = (int)(outputdur * 44100 + 0.5); // assume 44100 srate
}
outputdata.setSize(outputsize);
outputdata.setAll(0.0);
doGranulations(inputdata, outputdata);
int i;
// apply the amplitude envelope to the output soundfile if one is given
if (options.getBoolean("amplitude")) {
Envelope amp(options.getString("amp-env"), outputdata.getSize());
for (i=0; i<outputdata.getSize(); i++) {
amp.tick(i);
outputdata[i] *= amp.output(0);
}
}
// normalize the data to specified max amplitude if requested
if (options.getBoolean("normalize")) {
double max = -1;
for (i=0; i<outputdata.getSize(); i++) {
if (fabs(outputdata[i]) > max) {
max = fabs(outputdata[i]);
}
}
double scale = options.getDouble("normalization") / max;
for (i=0; i<outputdata.getSize(); i++) {
outputdata[i] *= scale;
}
}
// write the sound data to the output file
SoundHeader header;
header.setHighMono();
SoundFileWrite outsound(options.getArg(2), header);
for (i=0; i<outputdata.getSize(); i++) {
outsound.writeSampleDouble(outputdata[i]);
}
return 0;
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// doGranulations -- do all of the work.
//
void doGranulations(CircularBuffer& inputdata, Array& outputdata) {
Array<voiceItem> grains;
grains.setSize(1000000);
grains.setGrowth(1000000);
grains.setSize(0);
Array<int> voices(outputdata.getSize());
Array<int> grainsize(outputdata.getSize());
Array<int> grainvarsize(outputdata.getSize());
Array<int> gapsize(outputdata.getSize());
Array<int> gapvarsize(outputdata.getSize());
Array<int> transitions(outputdata.getSize());
Envelope voiceenv(voiceenv, outputdata.getSize());
Envelope grainsizeenv(grainenv, outputdata.getSize());
Envelope grainvarsizeenv(grainvarenv, outputdata.getSize());
Envelope transitionenv(transenv, outputdata.getSize());
Envelope gapsizeenv(gapenv, outputdata.getSize());
Envelope gapvarsizeenv(gapvarenv, outputdata.getSize());
int maxvoice = 0;
int i;
for (i=0; i<outputdata.getSize(); i++) {
voiceenv.tick(i);
voices[i] = int(voiceenv.output(0) + 0.5);
if (voices[i] > maxvoice) {
maxvoice = voices[i];
}
grainsizeenv.tick(i);
grainsize[i] = (int)(grainsizeenv.output(0)/1000.0*44100.0 + 0.5);
grainvarsizeenv.tick(i);
grainvarsize[i] = (int)(grainvarsizeenv.output(0)/1000.0*44100.0 + 0.5);
gapsizeenv.tick(i);
gapsize[i] = (int)(gapsizeenv.output(0)/1000.0*44100.0 + 0.5);
gapvarsizeenv.tick(i);
gapvarsize[i] = (int)(gapvarsizeenv.output(0)/1000.0*44100.0 + 0.5);
transitionenv.tick(i);
transitions[i] = (int)(transitionenv.output(0)/1000.0*44100.0 + 0.5);
}
for (i=1; i<=maxvoice; i++) {
addVoiceGrains(grains, i, voices, grainsize, grainvarsize, gapsize,
gapvarsize, transitions);
}
if (verboseQ) {
for (i=0; i<grains.getSize(); i++) {
cout << "Grain " << i+1 << ":\t";
grains[i].print();
}
}
for (i=0; i<grains.getSize(); i++) {
insertGrain(outputdata, inputdata, grains[i]);
}
}
//////////////////////////////
//
// insertGrain --
//
void insertGrain(Array<double>& outputdata, CircularBuffer<double>& inputdata,
voiceItem& grain) {
if (densityQ) {
outputdata[grain.start] = 0.1;
// return;
}
static Array<double> tStart(0);
static Array<double> tStop(0);
if (grain.transition != tStart.getSize()) {
tStart.setSize(grain.transition);
makeHalfWindow (tStart, 0);
}
if (grain.transition != tStop.getSize()) {
tStop.setSize(grain.transition);
makeHalfWindow (tStop, -1);
}
int i;
int inputstart = rand() % (inputdata.getSize()-22050);
// put the starting window on.
for (i=0; i<tStart.getSize(); i++) {
outputdata[grain.start + i] += inputdata[inputstart + i] * tStart[i];
}
// copy the constant portion of the grain
for (i=0; i<grain.grainsize; i++) {
outputdata[grain.start + tStart.getSize() + i] +=
inputdata[inputstart + tStart.getSize() + i];
}
// put the trailing window on.
for (i=0; i<tStop.getSize(); i++) {
outputdata[grain.start + grain.grainsize + tStart.getSize() + i] +=
inputdata[inputstart + grain.grainsize + tStart.getSize() + i] * tStop[i];
}
}
//////////////////////////////
//
// addVoiceGrains --
//
void addVoiceGrains(Array<voiceItem>& grains, int voice,
Array<int>& voices, Array<int>& grainsize, Array<int>& grainvarsize,
Array<int>& gapsize, Array<int>& gapvarsize, Array<int>& transitions) {
voiceItem tempitem;
int gapsamples;
int i;
for (i=0; i<voices.getSize(); i++) {
if (voice <= voices[i]) {
tempitem.voice = voice;
tempitem.grainsize = randomvar(grainsize[i], grainvarsize[i]);
tempitem.transition = transitions[i];
tempitem.start = i;
tempitem.offset = rand() % 1000;
gapsamples = randomvar(gapsize[i], gapvarsize[i]);
if (tempitem.start+tempitem.transition*2+tempitem.grainsize <
voices.getSize()) {
grains.append(tempitem);
}
i += tempitem.transition * 2 + tempitem.grainsize + gapsamples;
}
}
}
//////////////////////////////
//
// randomvar --
//
int randomvar(int base, int var) {
int value = (int)(base + var * (2.0 * rand()/RAND_MAX - 1.0) + 0.5);
if (value < 0) {
value = 0;
}
return value;
}
//////////////////////////////
//
// makeHalfWindow --
//
#define MY_PI 3.1415926535897932384626
#ifndef TWOPI
#define TWOPI 6.2831853
#endif
void makeHalfWindow(Array& window, int direction) {
int samples = window.getSize();
int i;
double phase = 0.0;
if (direction == -1) {
phase = MY_PI/2;
}
double freq = 1.0 / (samples * 4);
for (i=0; i<samples; i++) {
window[i] = sin(TWOPI * freq * i + phase);
window[i] *= window[i];
}
}
//////////////////////////////
//
// fillInputBuffer -- read in the input sound which is source material
// for the granulation.
//
void fillInputBuffer(CircularBuffer<double>& inputsound,
const char* infile, double start, double dur) {
SoundFileRead soundfile(infile);
int startsample = (int) (start * soundfile.getSrate());
soundfile.gotoSample(startsample);
int samplecount = 0;
if (duration <= 0) {
samplecount = soundfile.getSamples() - startsample;
} else {
samplecount = (int)(duration * soundfile.getSrate());
}
inputsound.setSize(samplecount);
inputsound.reset();
int i;
for (i=0; i<samplecount; i++) {
inputsound[i] = soundfile.getCurrentSampleDouble(0); // 0 = first chan.
soundfile.incrementSample();
}
}
//////////////////////////////
//
// checkOptions -- handle command-line options.
//
void checkOptions(Options& opts) {
opts.define("a|amp|amplitude|amp-env=s:0 1 1 1");
opts.define("n|norm|normalize|normalization=d:1.0");
opts.define("d|dur|duration=d:-1.0 seconds");
opts.define("od|output-duration=d:-1.0 seconds");
opts.define("s|start-time=d:0.0 seconds");
opts.define("v|voices|voice-env=s:0 1 1 1");
opts.define("g|grain|grain-env=s:0 50 1 50");
opts.define("gv|grain-var|grain-var-env=s:0 0 1 0");
opts.define("gap|gap-env=s:0 50 1 50");
opts.define("gapv|gap-var|gap-var-env=s:0 0 1 0");
opts.define("t|transition-envelope|transenv=s:0 5 1 5");
opts.define("verbose=b");
opts.define("density=b");
opts.define("author=b");
opts.define("version=b");
opts.define("example=b");
opts.define("help=b");
opts.process();
if (opts.getBoolean("author")) {
cout << "Written by Craig Stuart Sapp, "
<< "craig@ccrma.stanford.edu, Oct 2002" << endl;
exit(0);
}
if (opts.getBoolean("version")) {
cout << "compiled: " << __DATE__ << endl;
cout << SIG_VERSION << endl;
exit(0);
}
if (opts.getBoolean("help")) {
usage(opts.getCommand());
exit(0);
}
if (opts.getBoolean("example")) {
example();
exit(0);
}
// can only have one input and one output filename
if (opts.getArgCount() != 2) {
cout << "Error: need one input and one output file name." << endl;
usage(opts.getCommand());
exit(1);
}
starttime = opts.getDouble("start-time");
duration = opts.getDouble("duration");
voiceenv = opts.getString("voice-env");
grainenv = opts.getString("grain-env");
grainvarenv= opts.getString("grain-var-env");
gapenv = opts.getString("gap-env");
gapvarenv = opts.getString("gap-var-env");
outputdur = opts.getDouble("output-duration");
transenv = opts.getString("transition-envelope");
verboseQ = opts.getBoolean("verbose");
densityQ = opts.getBoolean("density");
cout << "Start time = " << starttime << endl;
cout << "duration = " << duration << endl;
}
//////////////////////////////
//
// example -- gives example calls to the osc program.
//
void example(void) {
cout <<
"# osc examples: \n"
"# a quick sweep in frequency from 0 to 22050 Hz with a saw-like \n"
"# amplitude envelope: \n"
" osc chirp.snd -d 0.5 -f \"0 0 1 22050\" -a \"0 0 1 1 10 0\" \n"
<< endl;
}
//////////////////////////////
//
// usage -- how to run the osc program on the command line.
//
void usage(const char* command) {
cout <<
" \n"
"Creates a frequency and amplitude varying sinusoid. \n"
" \n"
"Usage: " << command << " [-d duration][-f freqEnv][-a ampEnv] outsound \n"
" \n"
"Options: \n"
" -d = duration of the input soundfile to use. \n"
" -s = start time in seconds. \n"
" -a = amplitude envelope (default \"0 1, 1 1\"). \n"
" --options = list of all options, aliases and default values. \n"
" \n"
" \n"
<< endl;
}
// md5sum: a842d2ddfaf67cafdb103f318799c704 vogran.cpp [20050403]