Skip to content

transpose notes in cycles with target 't' using signed integers#114

Draft
unlessgames wants to merge 1 commit into
devfrom
cycle-transpose
Draft

transpose notes in cycles with target 't' using signed integers#114
unlessgames wants to merge 1 commit into
devfrom
cycle-transpose

Conversation

@unlessgames
Copy link
Copy Markdown
Collaborator

A draft for #93

Adding a new allowed target type in cycles using t that is used in the emitter to transpose the underlying note before output.

It seems to work well and opens up some nice techniques around transposing patterns across time with alternating values etc.

A couple of things that are kinda off with this:

  • Since transposition mainly makes sense with integers I had to add integers after a target in the grammar, but as stuff like t1 could just be a name, I opted for only allowing signed integers in the short form, so a:t+1 is a valid transpose but a:t1 is not. When using patterns for the target like [a b c]:t=<0 1 2 3>, they don't have to be signed.

Note, it would be nice to have detune features so fractional transpose would make sense, but this is a separate feature for pattrns in general.

  • Under the hood, the "t" is also just a NamedFloat, not a new "NamedInt", I guess this is a bit misleading but probably fine and keeps the code simpler.
  • While for setting properties, it make sense to have the innermost value override the outer, for transposition, it could be handy to allow for accumulation (for example [[a b c]:t=<0 7 12>]:t=<0 3>, where the two offsets would get added together instead of the <0 3> part being discarded. For a generalized syntax something like :t+=... could work, this might also be nice for other properties like volume etc.

As an alternative to all this, I've experimented with a more generic way to allow for transposition inside the cycle (instead of leaving it for the emitter like in this PR). This isn't ready for testing yet, but still relevant here. In theory it would allow us to define operators to combine patterns, like

[a b c] &+ <0 1 2>

where & would be a prefix for all existing operators and &+ would just be a generic addition that would work on constant types inside a cycle, so note + number or note + note would be a transposition, but number + number would be a regular addition (so you could generate number streams for anything like [a b c]:t=[<0 1 2> &+ <12 24 -12>]).

This might be a good way to leave an escape hatch in the syntax for all sorts of numeric operations and even other functionality like [a b c d] &swing 0.3.


Bringing this up because the simpler transposition implemented here overlaps with such plans and it might be better to only have one way to transpose notes.

@github-actions
Copy link
Copy Markdown

Benchmark for f9cab99

Click to view benchmark
Test Base PR %
Cycle/Generate 52.0±0.62µs 51.5±0.47µs -0.96%
Cycle/Parse 314.9±4.34µs 334.5±4.08µs +6.22%
Rust Phrase/Clone 455.7±3.23ns 433.7±13.76ns -4.83%
Rust Phrase/Create 66.9±0.77µs 71.4±1.45µs +6.73%
Rust Phrase/Run 630.4±7.42µs 619.6±9.38µs -1.71%
Rust Phrase/Seek 132.5±243.02µs 132.8±242.51µs +0.23%
Scripted Phrase/Clone 622.4±10.88ns 627.1±6.53ns +0.76%
Scripted Phrase/Create 1027.2±36.29µs 1015.0±40.46µs -1.19%
Scripted Phrase/Run 1666.2±10.35µs 1642.1±21.45µs -1.45%
Scripted Phrase/Seek 218.1±440.04µs 215.3±433.34µs -1.28%

@emuell
Copy link
Copy Markdown
Member

emuell commented May 12, 2026

Looks straight-forward so far. I'll give that an in depth try tomorrow.

a:t+1 is a valid transpose but a:t1 is not

Same with glide, where a:g1 is not valid but a:g1.0 is, which isn't really obvious.

Have you considered allowing/using floating point numbers for transpose, which also allow fractional tuning? Then we could at least also use the same ugly floating point number syntax here instead of the + prefix.

Using, or at least reserving, & for future use makes sense, too. But that probably needs a more thorough planning?

@unlessgames
Copy link
Copy Markdown
Collaborator Author

Have you considered allowing/using floating point numbers for transpose, which also allow fractional tuning? Then we could at least also use the same ugly floating point number syntax here instead of the + prefix.

So currently floating point number are allowed and the values kept as-is, this signed integer notation is an addition to the current syntax of all targets, not a different case just for t (the cycle still has no notion about what targets are valid, only the difference between instrument index and named floats). At output time the stored float is rounded inside the emitter to get an integer for transposition.

I think restricting to only be able to use floats in the syntax for the singular case is worse than having to add + or -, since transposition is usually done by integers.

It would be great to have fractional tuning but I assumed that is out of scope since the Note type is a byte. Maybe going with a new detune field on the NoteEvent could make the most sense here and would be easy to translate to MIDI.

Using, or at least reserving, & for future use makes sense, too. But that probably needs a more thorough planning?

Sure, I'll push my wip branch soon so it is easier to discuss.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants