diff --git a/sources/Application/Instruments/CommandList.cpp b/sources/Application/Instruments/CommandList.cpp index b01d932a0..2f9e2a2d9 100644 --- a/sources/Application/Instruments/CommandList.cpp +++ b/sources/Application/Instruments/CommandList.cpp @@ -38,6 +38,7 @@ static FourCC _all[] = { FourCC::InstrumentCommandTable, FourCC::InstrumentCommandTempo, FourCC::InstrumentCommandVelocity, + FourCC::InstrumentCommandVibrato, FourCC::InstrumentCommandVolume, }; diff --git a/sources/Application/Instruments/SRPUpdaters.cpp b/sources/Application/Instruments/SRPUpdaters.cpp index 15a6d7216..f293e0322 100644 --- a/sources/Application/Instruments/SRPUpdaters.cpp +++ b/sources/Application/Instruments/SRPUpdaters.cpp @@ -8,8 +8,10 @@ */ #include "SRPUpdaters.h" +#include "Foundation/Constants/SineTable.h" #include "System/Console/Trace.h" #include + // // Volume Ramp // @@ -298,3 +300,35 @@ void Arp::UpdateSRP(struct RUParams &rup) { return; rup.speedOffset_ = fp_mul(rup.speedOffset_, current_); }; + +// +// Vibrato +// + +void Vibrato::Reset() { + phase_ = 0; + current_ = FP_ONE; +}; + +void Vibrato::SetData(uint8_t rate, uint8_t depth) { + // lower 8 bits: depth, upper 8 bits: speed + depth_ = depth; + rate_ = 1 + (rate << 4); +}; + +void Vibrato::Trigger(bool tableTick) { + if (!enabled_ || tableTick) + return; + + // step the lfo phase + phase_ += rate_; + // sine is i16, depth u8 -> 23 bit value, shifted down by 10 bits gives a + // range of 13 bits (~±0.26 -> four semitones) + current_ = FP_ONE + ((sine_i16_interpolated_256(phase_) * depth_) >> 10); +}; + +void Vibrato::UpdateSRP(struct RUParams &rup) { + if (!enabled_) + return; + rup.speedOffset_ = fp_mul(rup.speedOffset_, current_); +} diff --git a/sources/Application/Instruments/SRPUpdaters.h b/sources/Application/Instruments/SRPUpdaters.h index a64efdd54..405063cb9 100644 --- a/sources/Application/Instruments/SRPUpdaters.h +++ b/sources/Application/Instruments/SRPUpdaters.h @@ -113,14 +113,20 @@ class Panner : public I_SRPUpdater { fixed speed_; }; -/*class Vibrato: public I_SRPUpdater { +class Vibrato : public I_SRPUpdater { public: - Vibrato() {} ; - virtual ~Vibrato() {} ; - void SetData() ; - virtual void Trigger(bool tableTick) ; - virtual void UpdateSRP(struct RUParams &rup) ; + Vibrato(){}; + virtual ~Vibrato(){}; + void Reset(); + void SetData(uint8_t rate, uint8_t depth); + virtual void Trigger(bool tableTick); + virtual void UpdateSRP(struct RUParams &rup); + private: -} ; -*/ + fixed current_; + uint8_t depth_; + uint16_t phase_; + uint16_t rate_; +}; + #endif diff --git a/sources/Application/Instruments/SampleInstrument.cpp b/sources/Application/Instruments/SampleInstrument.cpp index 78a9fd068..46bba8a3f 100644 --- a/sources/Application/Instruments/SampleInstrument.cpp +++ b/sources/Application/Instruments/SampleInstrument.cpp @@ -10,10 +10,10 @@ #include "SampleInstrument.h" #include "Application/Instruments/Filters.h" #include "Application/Model/Table.h" -#include "Application/Player/PlayerMixer.h" // For MIX_BUFFER_SIZE.. kick out pls #include "Application/Player/SyncMaster.h" #include "Application/Utils/fixed.h" #include "CommandList.h" +#include "Foundation/Constants/SineTable.h" #include "SamplePool.h" #include "SampleVariable.h" #include "Services/Audio/Audio.h" @@ -398,6 +398,7 @@ bool SampleInstrument::Start(int channel, unsigned char midinote, rp->volume_ = rp->baseVolume_ = i2fp(volume_.GetInt()); rp->pan_ = rp->basePan_ = i2fp(pan_.GetInt()); + rp->vibrato_.Reset(); if (!source_->IsMulti()) { rp->rendLoopStart_ = loopStart_.GetInt(); @@ -1433,6 +1434,19 @@ void SampleInstrument::ProcessCommand(int channel, FourCC cc, ushort value) { if (crush > 0) rp->crush_ = crush; } + + case FourCC::InstrumentCommandVibrato: { + uint8_t rate = value >> 8; + uint8_t depth = value & 0xFF; + // setup the vibrato + rp->vibrato_.SetData(rate, depth); + if (!rp->vibrato_.Enabled()) { + // enable and add to active updaters + rp->vibrato_.Enable(); + rp->activeUpdaters_.push_back(&rp->vibrato_); + } + } break; + default: break; }; diff --git a/sources/Application/Instruments/SampleRenderingParams.h b/sources/Application/Instruments/SampleRenderingParams.h index ae3327033..53e5fa9df 100644 --- a/sources/Application/Instruments/SampleRenderingParams.h +++ b/sources/Application/Instruments/SampleRenderingParams.h @@ -70,6 +70,7 @@ struct renderParams { LogSpeedRamp legato_; LogSpeedRamp pfin_; Arp arp_; + Vibrato vibrato_; bool couldClick_; diff --git a/sources/Application/Utils/HelpLegend.h b/sources/Application/Utils/HelpLegend.h index 5ac6e7049..ed452a228 100644 --- a/sources/Application/Utils/HelpLegend.h +++ b/sources/Application/Utils/HelpLegend.h @@ -121,6 +121,10 @@ static char **getHelpLegend(FourCC command) { result[0] = (char *)("MIDI Chord:abcd"); result[1] = (char *)("send rel notes:+a,+b,+c,+d"); break; + case FourCC::InstrumentCommandVibrato: + result[0] = (char *)("VIBrato:aabb"); + result[1] = (char *)("rate aa, depth bb"); + break; default: result[0] = result[1] = (char *)(""); break; diff --git a/sources/Foundation/Constants/SineTable.h b/sources/Foundation/Constants/SineTable.h new file mode 100644 index 000000000..dee8ecd71 --- /dev/null +++ b/sources/Foundation/Constants/SineTable.h @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 xiphonics, inc. + * + * This file is part of the picoTracker firmware + */ + +#pragma once + +// 256-step (+ sentinel) sine wave lookup table (full cycle) +// sin(2 * pi * index / 256) * 32767 +constexpr int16_t sine_i16_64[257] = { + 0, 804, 1608, 2410, 3212, 4011, 4808, 5602, 6393, + 7179, 7962, 8739, 9512, 10278, 11039, 11793, 12539, 13279, + 14010, 14732, 15446, 16151, 16846, 17530, 18204, 18868, 19519, + 20159, 20787, 21403, 22005, 22594, 23170, 23731, 24279, 24811, + 25329, 25832, 26319, 26790, 27245, 27683, 28105, 28510, 28898, + 29268, 29621, 29956, 30273, 30571, 30852, 31113, 31356, 31580, + 31785, 31971, 32137, 32285, 32412, 32521, 32609, 32678, 32728, + 32757, 32767, 32757, 32728, 32678, 32609, 32521, 32412, 32285, + 32137, 31971, 31785, 31580, 31356, 31113, 30852, 30571, 30273, + 29956, 29621, 29268, 28898, 28510, 28105, 27683, 27245, 26790, + 26319, 25832, 25329, 24811, 24279, 23731, 23170, 22594, 22005, + 21403, 20787, 20159, 19519, 18868, 18204, 17530, 16846, 16151, + 15446, 14732, 14010, 13279, 12539, 11793, 11039, 10278, 9512, + 8739, 7962, 7179, 6393, 5602, 4808, 4011, 3212, 2410, + 1608, 804, 0, -804, -1608, -2410, -3212, -4011, -4808, + -5602, -6393, -7179, -7962, -8739, -9512, -10278, -11039, -11793, + -12539, -13279, -14010, -14732, -15446, -16151, -16846, -17530, -18204, + -18868, -19519, -20159, -20787, -21403, -22005, -22594, -23170, -23731, + -24279, -24811, -25329, -25832, -26319, -26790, -27245, -27683, -28105, + -28510, -28898, -29268, -29621, -29956, -30273, -30571, -30852, -31113, + -31356, -31580, -31785, -31971, -32137, -32285, -32412, -32521, -32609, + -32678, -32728, -32757, -32767, -32757, -32728, -32678, -32609, -32521, + -32412, -32285, -32137, -31971, -31785, -31580, -31356, -31113, -30852, + -30571, -30273, -29956, -29621, -29268, -28898, -28510, -28105, -27683, + -27245, -26790, -26319, -25832, -25329, -24811, -24279, -23731, -23170, + -22594, -22005, -21403, -20787, -20159, -19519, -18868, -18204, -17530, + -16846, -16151, -15446, -14732, -14010, -13279, -12539, -11793, -11039, + -10278, -9512, -8739, -7962, -7179, -6393, -5602, -4808, -4011, + -3212, -2410, -1608, -804, 0}; + +// 16bit interpolation for 256-step sine table +static inline int32_t sine_i16_interpolated_256(uint16_t phase) { + // index into the table (top 8 bits) and fractional part (lower 8 bits) + uint8_t index = phase >> 8; // 0..255 + uint16_t frac = phase & 0xFF; // 0..255 + + // fetch table values + int32_t a = sine_i16_64[index]; + int32_t b = sine_i16_64[index + 1]; + + // a + ((b - a) * frac) / 256 + return a + (((b - a) * frac) >> 8); +} diff --git a/sources/Foundation/Types/Types.h b/sources/Foundation/Types/Types.h index 414624258..fe32c0bc8 100644 --- a/sources/Foundation/Types/Types.h +++ b/sources/Foundation/Types/Types.h @@ -45,6 +45,7 @@ struct FourCC { InstrumentCommandVolume = 69, // VOLM InstrumentCommandNone = 45, // ---- InstrumentCommandMidiChord = 143, + InstrumentCommandVibrato = 73, SampleInstrumentCrushVolume = 3, SampleInstrumentVolume = 19, @@ -296,6 +297,7 @@ struct FourCC { ETL_ENUM_TYPE(InstrumentCommandDelay, "DLY") ETL_ENUM_TYPE(InstrumentCommandInstrumentRetrigger, "IRT") ETL_ENUM_TYPE(InstrumentCommandMidiChord, "MCH") + ETL_ENUM_TYPE(InstrumentCommandVibrato, "VIB") ETL_ENUM_TYPE(VarLineOut, "LINEOUT") ETL_ENUM_TYPE(VarMidiDevice, "MIDIDEVICE") diff --git a/usermanual/advance-edition/content/pages/commands.md b/usermanual/advance-edition/content/pages/commands.md index 4571b9c27..ef4df9086 100644 --- a/usermanual/advance-edition/content/pages/commands.md +++ b/usermanual/advance-edition/content/pages/commands.md @@ -201,6 +201,14 @@ RTG 0101: does not do anything because after looping one tick, you move forward Sets the MIDI note velocity value (`bb`) for MIDI instruments. This is valid for MIDI instruments *only* and can also be used in tables. +## VIB aabb + +**Applies a vibrato using a sine LFO for pitch modulation: `aa` is the rate and `bb` is the depth.** + +- `aa` sets how fast the vibrato cycles (`00` is slowest at ~0.05 Hz, `FF` fastest at ~30.0 Hz) +- `bb` sets vibrato depth (`00` = no modulation, `FF` = maximum depth) +- at maximum depth, modulation range is roughly `+/-4` semitones + ## VOL aabb (VOLM in lgpt) **starting from the instrument's volume setting, approach volume bb at speed aa. 00 is the lowest volume and 00 is the fastest speed (instant).** diff --git a/usermanual/pico-edition/content/pages/commands.md b/usermanual/pico-edition/content/pages/commands.md index 4571b9c27..035033258 100644 --- a/usermanual/pico-edition/content/pages/commands.md +++ b/usermanual/pico-edition/content/pages/commands.md @@ -201,6 +201,16 @@ RTG 0101: does not do anything because after looping one tick, you move forward Sets the MIDI note velocity value (`bb`) for MIDI instruments. This is valid for MIDI instruments *only* and can also be used in tables. +## VIB aabb + + +**Applies a vibrato using a sine LFO for pitch modulation: `aa` is the rate and `bb` is the depth.** + +- `aa` sets how fast the vibrato cycles (`00` is slowest at ~0.05 Hz, `FF` fastest at ~30.0 Hz) +- `bb` sets vibrato depth (`00` = no modulation, `FF` = maximum depth) +- at maximum depth, modulation range is roughly `+/-4` semitones + + ## VOL aabb (VOLM in lgpt) **starting from the instrument's volume setting, approach volume bb at speed aa. 00 is the lowest volume and 00 is the fastest speed (instant).**