Added note_offset and octave_offset
This commit is contained in:
parent
8bc0a9c2f5
commit
ed7d5369a7
8 changed files with 125 additions and 10 deletions
|
|
@ -156,5 +156,7 @@ namespace Default
|
|||
// ***** SFZ v2 ********
|
||||
constexpr bool checkSustain { true }; // sustain_sw
|
||||
constexpr bool checkSostenuto { true }; // sostenuto_sw
|
||||
constexpr Range<int> octaveOffsetRange { -10, 10 }; // octave_offset
|
||||
constexpr Range<int> noteOffsetRange { -127, 127 }; // note_offset
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public:
|
|||
* @param value
|
||||
* @return Type
|
||||
*/
|
||||
Type clamp(Type value) const noexcept { return ::clamp(value, _start, _end); }
|
||||
constexpr Type clamp(Type value) const noexcept { return ::clamp(value, _start, _end); }
|
||||
/**
|
||||
* @brief Checks if a value is in the range, including the endpoints
|
||||
*
|
||||
|
|
@ -137,6 +137,13 @@ bool operator==(const sfz::Range<Type>& lhs, const sfz::Range<Type>& rhs)
|
|||
return (lhs.getStart() == rhs.getStart()) && (lhs.getEnd() == rhs.getEnd());
|
||||
}
|
||||
|
||||
template <class Type>
|
||||
bool operator!=(const sfz::Range<Type>& lhs, const sfz::Range<Type>& rhs)
|
||||
{
|
||||
return (lhs.getStart() != rhs.getStart()) || (lhs.getEnd() != rhs.getEnd());
|
||||
}
|
||||
|
||||
|
||||
template <class Type>
|
||||
bool operator==(const sfz::Range<Type>& lhs, const std::pair<Type, Type>& rhs)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -807,3 +807,57 @@ float sfz::Region::velocityCurve(uint8_t velocity) const noexcept
|
|||
|
||||
return gain;
|
||||
}
|
||||
|
||||
constexpr uint8_t offsetAndClamp(uint8_t key, int offset, sfz::Range<uint8_t> range)
|
||||
{
|
||||
const int offsetKey { key + offset };
|
||||
if (offsetKey > std::numeric_limits<uint8_t>::max())
|
||||
return range.getEnd();
|
||||
if (offsetKey < std::numeric_limits<uint8_t>::min())
|
||||
return range.getStart();
|
||||
|
||||
return range.clamp(static_cast<uint8_t>(offsetKey));
|
||||
}
|
||||
|
||||
void sfz::Region::offsetAllKeys(int offset) noexcept
|
||||
{
|
||||
// Offset key range
|
||||
if (keyRange != Default::keyRange) {
|
||||
const auto start = keyRange.getStart();
|
||||
const auto end = keyRange.getEnd();
|
||||
keyRange.setStart(offsetAndClamp(start, offset, Default::keyRange));
|
||||
keyRange.setEnd(offsetAndClamp(end, offset, Default::keyRange));
|
||||
}
|
||||
pitchKeycenter = offsetAndClamp(pitchKeycenter, offset, Default::keyRange);
|
||||
|
||||
// Offset key switches
|
||||
if (keyswitchRange != Default::keyRange) {
|
||||
const auto start = keyswitchRange.getStart();
|
||||
const auto end = keyswitchRange.getEnd();
|
||||
keyswitchRange.setStart(offsetAndClamp(start, offset, Default::keyRange));
|
||||
keyswitchRange.setEnd(offsetAndClamp(end, offset, Default::keyRange));
|
||||
}
|
||||
if (keyswitchUp)
|
||||
keyswitchUp = offsetAndClamp(*keyswitchUp, offset, Default::keyRange);
|
||||
if (keyswitch)
|
||||
keyswitch = offsetAndClamp(*keyswitch, offset, Default::keyRange);
|
||||
if (keyswitchDown)
|
||||
keyswitchDown = offsetAndClamp(*keyswitchDown, offset, Default::keyRange);
|
||||
if (previousNote)
|
||||
previousNote = offsetAndClamp(*previousNote, offset, Default::keyRange);
|
||||
|
||||
// Offset crossfade ranges
|
||||
if (crossfadeKeyInRange != Default::crossfadeKeyInRange) {
|
||||
const auto start = crossfadeKeyInRange.getStart();
|
||||
const auto end = crossfadeKeyInRange.getEnd();
|
||||
crossfadeKeyInRange.setStart(offsetAndClamp(start, offset, Default::keyRange));
|
||||
crossfadeKeyInRange.setEnd(offsetAndClamp(end, offset, Default::keyRange));
|
||||
}
|
||||
|
||||
if (crossfadeKeyOutRange != Default::crossfadeKeyOutRange) {
|
||||
const auto start = crossfadeKeyOutRange.getStart();
|
||||
const auto end = crossfadeKeyOutRange.getEnd();
|
||||
crossfadeKeyOutRange.setStart(offsetAndClamp(start, offset, Default::keyRange));
|
||||
crossfadeKeyOutRange.setEnd(offsetAndClamp(end, offset, Default::keyRange));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@ struct Region {
|
|||
*/
|
||||
bool parseOpcode(const Opcode& opcode);
|
||||
|
||||
void offsetAllKeys(int offset) noexcept;
|
||||
|
||||
uint32_t loopStart(Oversampling factor = x1) const noexcept;
|
||||
uint32_t loopEnd(Oversampling factor = x1) const noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
|||
parseOpcodes(groupOpcodes);
|
||||
parseOpcodes(regionOpcodes);
|
||||
|
||||
if (octaveOffset != 0 || noteOffset != 0)
|
||||
lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset);
|
||||
|
||||
regions.push_back(std::move(lastRegion));
|
||||
}
|
||||
|
||||
|
|
@ -186,6 +189,12 @@ void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
|
|||
defaultPath = absl::StrReplaceAll(trim(member.value), { { "\\", "/" } });
|
||||
DBG("Changing default sample path to " << defaultPath);
|
||||
break;
|
||||
case hash("note_offset"):
|
||||
setValueFromOpcode(member, noteOffset, Default::noteOffsetRange);
|
||||
break;
|
||||
case hash("octave_offset"):
|
||||
setValueFromOpcode(member, octaveOffset, Default::octaveOffsetRange);
|
||||
break;
|
||||
default:
|
||||
// Unsupported control opcode
|
||||
DBG("Unsupported control opcode: " << member.opcode);
|
||||
|
|
|
|||
|
|
@ -455,8 +455,10 @@ private:
|
|||
Resources resources;
|
||||
MidiState midiState;
|
||||
|
||||
// default_path
|
||||
// Control opcodes
|
||||
std::string defaultPath { "" };
|
||||
int noteOffset { 0 };
|
||||
int octaveOffset { 0 };
|
||||
|
||||
LEAK_DETECTOR(Synth);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -365,3 +365,46 @@ TEST_CASE("[Files] Set CC applies properly to all channels")
|
|||
REQUIRE(synth.getMidiState().getCCValue(channel, 61) == 122);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("[Files] Note and octave offsets")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/note_offset.sfz");
|
||||
REQUIRE( synth.getNumRegions() == 7 );
|
||||
|
||||
REQUIRE( synth.getRegionView(0)->keyRange == sfz::Range<uint8_t>(64, 64) );
|
||||
REQUIRE( synth.getRegionView(0)->pitchKeycenter == 64 );
|
||||
REQUIRE( synth.getRegionView(0)->keyswitchRange == sfz::Default::keyRange );
|
||||
REQUIRE( synth.getRegionView(0)->crossfadeKeyInRange == sfz::Default::crossfadeKeyInRange );
|
||||
REQUIRE( synth.getRegionView(0)->crossfadeKeyOutRange == sfz::Default::crossfadeKeyOutRange );
|
||||
|
||||
REQUIRE( synth.getRegionView(1)->keyRange == sfz::Range<uint8_t>(51, 56) );
|
||||
REQUIRE( synth.getRegionView(1)->pitchKeycenter == 51 );
|
||||
|
||||
REQUIRE( synth.getRegionView(2)->keyRange == sfz::Range<uint8_t>(41, 45) );
|
||||
REQUIRE( synth.getRegionView(2)->pitchKeycenter == 41 );
|
||||
REQUIRE( synth.getRegionView(2)->crossfadeKeyInRange == sfz::Range<uint8_t>(37, 41) );
|
||||
REQUIRE( synth.getRegionView(2)->crossfadeKeyOutRange == sfz::Range<uint8_t>(45, 49) );
|
||||
|
||||
REQUIRE( synth.getRegionView(3)->keyRange == sfz::Range<uint8_t>(62, 62) );
|
||||
REQUIRE( synth.getRegionView(3)->keyswitchRange == sfz::Range<uint8_t>(23, 27) );
|
||||
REQUIRE( synth.getRegionView(3)->keyswitch );
|
||||
REQUIRE( *synth.getRegionView(3)->keyswitch == 24 );
|
||||
REQUIRE( synth.getRegionView(3)->keyswitchUp );
|
||||
REQUIRE( *synth.getRegionView(3)->keyswitchUp == 24 );
|
||||
REQUIRE( synth.getRegionView(3)->keyswitchDown );
|
||||
REQUIRE( *synth.getRegionView(3)->keyswitchDown == 24 );
|
||||
REQUIRE( synth.getRegionView(3)->previousNote );
|
||||
REQUIRE( *synth.getRegionView(3)->previousNote == 61 );
|
||||
|
||||
REQUIRE( synth.getRegionView(4)->keyRange == sfz::Range<uint8_t>(76, 76) );
|
||||
REQUIRE( synth.getRegionView(4)->pitchKeycenter == 76 );
|
||||
|
||||
REQUIRE( synth.getRegionView(5)->keyRange == sfz::Range<uint8_t>(50, 50) );
|
||||
REQUIRE( synth.getRegionView(5)->pitchKeycenter == 50 );
|
||||
|
||||
REQUIRE( synth.getRegionView(6)->keyRange == sfz::Range<uint8_t>(50, 50) );
|
||||
REQUIRE( synth.getRegionView(6)->pitchKeycenter == 50 );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
<control> note_offset=1
|
||||
<region> key=63 sample=*sine
|
||||
<region> lokey=50 hikey=55 pitch_keycenter=50 sample=*sine
|
||||
<region> lokey=40 hikey=44 pitch_keycenter=40 xfin_lokey=36 xfin_hikey=40 xfout_lokey=36 xfout_hikey=40 sample=*sine
|
||||
<region> lokey=40 hikey=44 pitch_keycenter=40 xfin_lokey=36 xfin_hikey=40 xfout_lokey=44 xfout_hikey=48 sample=*sine
|
||||
<control> note_offset=-1
|
||||
<region> key=63 sample=*sine
|
||||
<region> lokey=50 hikey=55 pitch_keycenter=50 sample=*sine
|
||||
<region> lokey=40 hikey=44 pitch_keycenter=40 xfin_lokey=36 xfin_hikey=40 xfout_lokey=36 xfout_hikey=40 sample=*sine
|
||||
<region> key=63 sw_lokey=24 sw_hikey=28 sw_last=25 sw_up=25 sw_down=25 sw_previous=62 sample=*sine
|
||||
<control> note_offset=1 octave_offset=1
|
||||
<region> key=63 sample=*sine
|
||||
<region> lokey=50 hikey=55 pitch_keycenter=50 sample=*sine
|
||||
<region> lokey=40 hikey=44 pitch_keycenter=40 xfin_lokey=36 xfin_hikey=40 xfout_lokey=36 xfout_hikey=40 sample=*sine
|
||||
<control> note_offset=-1 octave_offset=-1
|
||||
<region> key=63 sample=*sine
|
||||
<region> lokey=50 hikey=55 pitch_keycenter=50 sample=*sine
|
||||
<region> lokey=40 hikey=44 pitch_keycenter=40 xfin_lokey=36 xfin_hikey=40 xfout_lokey=36 xfout_hikey=40 sample=*sine
|
||||
<control> // Check that this does not reset either note or octave offset
|
||||
<region> key=63 sample=*sine
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue