2024-07-15 11:46:59 +08:00

113 lines
3.5 KiB
C++

//
// Programmer: Ziemowit Laski <zlaski@ziemas.net>
// Creation Date: Sun, Jun 12, 2016 11:09:34 AM
// Last Modified: Sun, Jun 12, 2016 11:09:34 AM
// Filename: tools/stretch.cpp
// URL: https://github.com/craigsapp/midifile/blob/master/tools/stretch.cpp
// Syntax: C++11
// vim: ts=3
//
// Description: Stretches (or shrinks):
// 1. The position of bars (measures) in tracks,
// without affecting tempo, and/or
// 2. The tempo (BPM) itself.
//
// This is useful when working with MIDI files
// that were automatically generated from audio
// (MP3, WAV) files. Conversion software often
// has difficulty in correct placement of
// bars, and also sometimes does not preserve
// the tempo of the original.
//
#include "MidiFile.h"
#include "Options.h"
#include <algorithm>
#include <cmath>
#include <iostream>
using namespace std;
using namespace smf;
void doStretch (MidiFile& midifile, double bars, double duration);
///////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {
Options options;
options.define("b|bars|m|measures=d:1.0",
"Stretch width of bars (measures) by factor specified (WITHOUT affecting tempo)");
options.define("d|duration|t|tempo=d:1.0",
"Stretch duration of track(s) by factor specified");
options.process(argc, argv);
if (options.getArgCount() != 2) {
cerr << "two MIDI filenames are required.\n";
exit(1);
}
MidiFile midifile;
midifile.read(options.getArg(1));
if (!midifile.status()) {
cerr << "Error reading MIDI file " << options.getArg(1) << endl;
exit(1);
}
if (options.getBoolean("bars") || options.getBoolean("duration")) {
double bars = options.getDouble("bars");
double duration = options.getDouble("duration");
if (bars < 0.1 || bars > 10.0 || duration < 0.1 || duration > 10.0) {
cerr << "stretch parameters must be between 0.1 and 10.\n";
exit(1);
}
doStretch(midifile, bars, duration);
}
midifile.write(options.getArg(2));
return 0;
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// doStretch -- Stretch size of all bars (measures) in the file
// by a specified 'bars' factor. (Values lower than 1.0 will result
// in shrinking). Note that bars (measures) are not encoded in MIDI
// directly as an event type. Instead, they are computed from the
// Pulse Per Quarter Note (PPQN) value in the file header; PPQN
// multiplied by the number of quarter notes in a measure (as
// determined by the time signature contained in the FF 58
// message) gives the number of ticks in each measure. The
// 'duration' parameter specifies the factor by which the
// duration of the track(s) should be stretched (or shrunk,
// if less than 1.0).
void doStretch(MidiFile& midifile, double bars, double duration) {
int ppqn = midifile.getTicksPerQuarterNote();
int new_ppqn = max( 24576, ppqn );
midifile.setTicksPerQuarterNote( new_ppqn );
double tick_mult = (double)new_ppqn / (double)ppqn;
tick_mult /= bars;
for (int t = 0; t < midifile.size(); ++t) {
MidiEventList &track = midifile[t];
for (int e = 0; e < track.size(); ++e) {
MidiEvent &event = track[e];
event.tick *= (int)(event.tick * tick_mult);
if (event.getMetaType() == 0x51) {
double tempo = event.getTempoMicroseconds();
tempo *= bars;
tempo *= duration;
event.setTempoMicroseconds( (int)tempo );
}
}
}
}