vibrato, `,` shortcut in pattern editor

portability/boost
zetaPRIME 2019-01-28 04:57:50 -05:00
parent d8ec92a9c6
commit ac600dadd6
6 changed files with 75 additions and 28 deletions

22
notes
View File

@ -25,6 +25,8 @@ parameters {
vXX - volume; 00 .. FF -> 0.0 .. 1.0 (accepts tweens)
pXX - panning; signed byte? 00 as center (accepts tweens)
gXX/GXX - glissando (pitch bend); g=down, G=up; relative semitones (accepts tweens)
rXX - vibrato; first byte is ticks per full cycle, second byte is how much to bend (0x10 == 1.0, defaults to 0.25)
^ if first byte is 00, keeps speed if bend specified, else stops vibrato
}
global port (G) {
@ -34,23 +36,25 @@ parameters {
TODO {
immediate frontburner {
- instrumentcore tweens (unordered_multimap...)
- iterator/reader abstraction for commands
- actual support for commands in InstrumentCore
node function to release unneeded old data when stopping playback?
note-agnostic params in InstrumentCore (VXX, PXX)
custom param functions
- vibrato!
tremolo?
}
indexer abstraction for audioports (assign/add std::pair<float, float>)
maybe a similar abstraction for processing notes to what commandreader does
node function to release unneeded old data when stopping playback?
# make knob notches more even (currently "previous value" is twice as big as any other step at px>1)
add standardized step values for knobs (int enum?)
include Arcon (rounded) for use as unified patchboard font?
bugs to fix {
-? graph connections sometimes spawn in duplicated :|
- on starting playback, sometimes a "thunk" sneaks into the waveform?
-? buffer underruns are being caused by some sync wonkiness between multiple workers
it sometimes crashes on exit??
}
misc features needed before proper release {

View File

@ -41,7 +41,7 @@ namespace Xybrid::NodeLib {
inline uint8_t param() const { return cr.param(static_cast<uint8_t>(pn)); }
inline uint8_t val() const { return cr.val(static_cast<uint8_t>(pn)); }
int16_t next(bool acceptsTweens, uint8_t = 1) const;
int16_t next(bool acceptsTweens = false, uint8_t = 1) const;
int16_t tween() const;
};
}

View File

@ -80,7 +80,7 @@ void InstrumentCore::process(CommandPort* i, AudioPort* o) {
notePtr = &note;
//auto& note = sc.first->second;
if (!sc.second) {
removeTweens(note, &note.note); // stop any note-value tweens
removeTween(note, &note.note); // stop any note-value tweens
if (note.adsrPhase == 2) { note = Note(id); goto forceRetrigger; } // reinstantiate on replace
if (cr.numParams() > 0 && cr.param(0) == 't') {
startTween(note, &note.note, n, 0, cr.val(0));
@ -119,7 +119,7 @@ void InstrumentCore::process(CommandPort* i, AudioPort* o) {
double vol = (1.0*v) / 255.0;
auto t = pr.tween();
if (t <= 0) {
removeTweens(note, &note.volume);
removeTween(note, &note.volume);
note.volume = vol;
} else startTween(note, &note.volume, vol, shortStep, t);
break;
@ -128,7 +128,7 @@ void InstrumentCore::process(CommandPort* i, AudioPort* o) {
double pan = std::clamp((1.0*static_cast<int8_t>(v)) / 127.0, -1.0, 1.0);
auto t = pr.tween();
if (t <= 0) {
removeTweens(note, &note.pan);
removeTween(note, &note.pan);
note.pan = pan;
} else startTween(note, &note.pan, pan, shortStep, t);
break;
@ -140,11 +140,23 @@ void InstrumentCore::process(CommandPort* i, AudioPort* o) {
if (p == 'g') nd *= -1.0;
auto t = pr.tween();
if (t <= 0) {
removeTweens(note, &note.note);
removeTween(note, &note.note);
note.note += nd;
} else startTween(note, &note.note, note.note + nd, shortStep, t);
break;
}
case 'r': {
auto v2 = pr.next();
Tween* t = findTween(note, &note.noteAdd);
if (!t) {
if (v == 0) break; // don't bother with a stopped tween
t = &(startTween(note, &note.noteAdd, 0.25, 0, v));
t->flags |= 2;
} else if (v2 == -1) v2 = 0; // 00 with no second param to stop
if (v > 0) t->ticksLeft = v;
if (v2 != -1) t->valEnd = (1.0/16.0)*v2;
break;
}
default:
break;
}
@ -191,7 +203,7 @@ void InstrumentCore::advanceNote(Note& n) {
n.time += smpTime;
n.adsrTime += smpTime;
for (auto it = ni.tweenSet.first; it != ni.tweenSet.second; ++it) it->second.process(n);
for (auto it = ni.tweenSet.first; it != ni.tweenSet.second; ++it) it->second.process(n, smpTime);
if (n.adsrPhase == 0) {
if (n.adsrTime > n.adsr.a) {
@ -216,7 +228,7 @@ void InstrumentCore::deleteNote(Note& n) {
}
}
void InstrumentCore::removeTweens(InstrumentCore::Note& n, double* op) {
void InstrumentCore::removeTween(InstrumentCore::Note& n, double* op) {
auto r = activeTweens.equal_range(n.id);
for (auto it = r.first; it != r.second; ) {
if (it->second.op == op) it = activeTweens.erase(it);
@ -225,13 +237,25 @@ void InstrumentCore::removeTweens(InstrumentCore::Note& n, double* op) {
}
void InstrumentCore::removeTweens(InstrumentCore::Note& n) { activeTweens.erase(n.id); }
Tween* InstrumentCore::findTween(InstrumentCore::Note& n, double* op) {
auto r = activeTweens.equal_range(n.id);
for (auto it = r.first; it != r.second; ++it) {
if (it->second.op == op) return &(it->second);
}
return nullptr;
}
Tween& InstrumentCore::startTween(InstrumentCore::Note& n, double* op, double val, double time, int16_t ticks) {
// remove anything already operating on the same note
removeTweens(n, op);
removeTween(n, op);
auto it = activeTweens.emplace(std::make_pair(n.id, Tween(n, op, val, time, ticks)));
return it->second;
}
double Note::effectiveNote() const {
return note + noteAdd;
}
double Note::ampMult() const {
double a = adsrVol(adsr, adsrPhase, adsrTime) * volume;
return a*a; // most synthesizers use a curve of 40log(vol) dB... which simplifies to vol^2
@ -251,15 +275,25 @@ Tween::Tween(InstrumentCore::Note& n, double* op, double val, double time, int16
void Tween::startTick(Note& n, double tickTime) {
if (ticksLeft >= 0) {
timeStart = n.time;
timeEnd = timeStart + tickTime * ticksLeft;
valStart = *op;
ticksLeft--;
if (flags & 2) { // sine mode
timeEnd = tickTime * ticksLeft;
} else {
timeStart = n.time;
timeEnd = timeStart + tickTime * ticksLeft;
valStart = *op;
ticksLeft--;
}
}
}
void Tween::process(Note& n) {
void Tween::process(Note& n, double smpTime) {
if (flags & 1) return; // already done
if (flags & 2) { // sine mode
if (timeEnd > 0) timeStart += smpTime / timeEnd;
*op = std::sin(timeStart * PI*2) * valEnd;
if (valEnd == 0.0) flags |= 1; // stop when magnitude set to 0
return;
}
if (timeEnd == timeStart) {
*op = valEnd; // instant
flags |= 1;

View File

@ -32,7 +32,8 @@ namespace Xybrid::NodeLib {
public:
uint16_t id;
double note; // floating point to allow smooth pitch bends
double time = 0;
double noteAdd = 0.0;
double time = 0.0;
double volume = 1.0;
double pan = 0.0;
@ -47,6 +48,7 @@ namespace Xybrid::NodeLib {
Note() = default;
Note(uint16_t id);
double effectiveNote() const;
double ampMult() const;
};
@ -69,7 +71,7 @@ namespace Xybrid::NodeLib {
Tween(Note&, double*, double val, double time, int16_t ticks = -1);
void startTick(Note&, double tickTime);
void process(Note&);
void process(Note&, double smpTime);
};
std::unordered_map<uint16_t, Note> activeNotes;
@ -92,10 +94,11 @@ namespace Xybrid::NodeLib {
void advanceNote(Note&);
void deleteNote(Note&);
/// Removes tweens matching the specified note and target.
void removeTweens(Note&, double*);
/// Removes tween matching the specified note and target.
void removeTween(Note&, double*);
/// Removes all tweens matching the specified note.
void removeTweens(Note&);
Tween* findTween(Note&, double*);
Tween& startTween(Note&, double*, double val, double time, int16_t ticks = -1);
};

View File

@ -108,7 +108,6 @@ void I2x03::init() {
};
core.processNote = [this](Note& note, AudioPort* p) {
//double enote = note.note;
double freq;// = 440.0 * std::pow(SEMI, enote - (45+12*2));
double smpTime = core.sampleTime();
@ -117,7 +116,7 @@ void I2x03::init() {
core.advanceNote(note);
double smp = 0.0;
double n = note.note;
double n = note.effectiveNote();
int8_t wave = this->wave;
if (note.time < blipTime) {
if (blipWave != -1) wave = blipWave;

View File

@ -295,6 +295,13 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
view->setCurrentIndex(index.siblingAtColumn(index.column()+1));
return dc->commit();
} else {
if (k == Qt::Key_Comma) { // convenience; allow inserting an extend from number column
if (row.numParams() >= PatternEditorModel::paramSoftCap) return dc->cancel();
row.insertParam(par+1, ',');
auto view = static_cast<PatternEditorView*>(parent());
view->setCurrentIndex(index.siblingAtColumn(index.column()+2));
return dc->commit();
}
for (size_t i = 0; i < 16; i++) {
if (k == numberKeys[i]) {
insertDigit(row.param(par)[1], i);