vibrato, `,` shortcut in pattern editor
parent
d8ec92a9c6
commit
ac600dadd6
22
notes
22
notes
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ void InstrumentCore::process(CommandPort* i, AudioPort* o) {
|
|||
notePtr = ¬e;
|
||||
//auto& note = sc.first->second;
|
||||
if (!sc.second) {
|
||||
removeTweens(note, ¬e.note); // stop any note-value tweens
|
||||
removeTween(note, ¬e.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, ¬e.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, ¬e.volume);
|
||||
removeTween(note, ¬e.volume);
|
||||
note.volume = vol;
|
||||
} else startTween(note, ¬e.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, ¬e.pan);
|
||||
removeTween(note, ¬e.pan);
|
||||
note.pan = pan;
|
||||
} else startTween(note, ¬e.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, ¬e.note);
|
||||
removeTween(note, ¬e.note);
|
||||
note.note += nd;
|
||||
} else startTween(note, ¬e.note, note.note + nd, shortStep, t);
|
||||
break;
|
||||
}
|
||||
case 'r': {
|
||||
auto v2 = pr.next();
|
||||
Tween* t = findTween(note, ¬e.noteAdd);
|
||||
if (!t) {
|
||||
if (v == 0) break; // don't bother with a stopped tween
|
||||
t = &(startTween(note, ¬e.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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue