cardonabits / haxo-rs

Software for the haxophone
MIT License
41 stars 10 forks source link

Transposing #19

Closed jcard0na closed 5 months ago

jcard0na commented 6 months ago

Suggested here:
https://github.com/cardonabits/haxo-hw/discussions/41#discussioncomment-7915720

This would be relatively simple:

  1. Choose an unused control key combination like the ones used for changing instruments. For instance, table G# and table Bb for +1/2 and -1/2

  2. Add transposition value to the function that translates fingering to midi note, see here

petermoz commented 6 months ago

Hi Javier, as someone who plays alto more than tenor, I'd like to take a look at the transposition function - but I had an idea for an alternative user interface. Rather than having to step up or down a semitone at a time, it'd be nice to be able to jump to a particular key - e.g. to switch to alto you'd enter transposition mode and then play an Eb. That'd be much easier than counting semitones.

The downside is that it makes the "menu" system more complicated. Instead of entering Mode::Control and pressing up or down buttons (like your suggestion), you'd have to enter another mode. To prevent the user getting lost in the menu system, I think it'd make sense for transposition mode to be independent of control mode, so rather than

# Not this
Play Mode -> (Low Bb + draw air) -> Control Mode -> (Button?) -> Transpose Mode -> ....

You'd instead do

Play Mode -> (Low B + draw air) -> Transpose Mode -> Press keys for Eb + blow air (sets offset) -> Play Mode

What do you think? I'd be happy to have a go at the implementation once we've worked out the UI

jcard0na commented 6 months ago

I like your proposal a lot, definitely better than my first draft. Two things that come to mind:

  1. Some people will probably want to change the transposition permanently. Currently the way to do this is by changing the default notemap. If one has a different notemap installed, selecting a transposition using absolute notes (e.g. Eb) will be trickier. The offset would have to be calculated relative to some note in the current notemap, and that note might have a different fingering. But I'm jumping into implementation details here.

  2. Can you post your proposed approach as a poll in haxo-hw? There are some musicians that should have valuable feedback on your proposed interface.

Thanks!

petermoz commented 6 months ago

Thanks Javier. I hadn't thought about the interaction with notemaps. I think there could be three options then

  1. Keep notemaps as the main way of changing between different "instruments" (tenor vs alto) and keep this transposition option for simple things like +/- a semitone

  2. Keep notemaps as is (i.e. the default is tenor), but use my proposal for changing the transposition. To calculate the offset, this would probably mean assuming the user hasn't changed the notemap

  3. Redefine the concept of the notemap so the note output by the notemap is the "written note". I.e. fingering for Bb -> note map outputs a Bb. Then it would be up to the transpose offset to deal with the conversion from "written note" to "sounding note". This would mean there would be a single notemap for bari / tenor / alto / soprano, and you'd use my suggestion above for changing the transposition. We could then add a command line argument for the default transposition (so a user could configure their own haxophone to boot as a tenor or alto, etc)

I think (3) is the best option, but would be a breaking change for people that have written their own notemaps. They could get the old functionality by setting the "transposition" to zero on the command line, though.

Sorry to delve into implementation details a bit, but I though it worth running (3) by you first. I'll post a poll to haxo-hw if you agree that those are the three options I should mention in my post.

Thanks!

jcard0na commented 6 months ago

Hi Peter (and happy new year!),

Yes, I like your 3rd option above. Let me capture my thinking to explain why I like it. I see the following use cases for transposition:

UC1 A user wants to boot by default into their default transposition (e.g. Bb instrument) UC2 A user wants to temporarily change transposition until next change or reboot, for instance to play along a recording in an unfamiliar key

Considerations:

  1. Only UC1 is supported at the moment, and that requires replacing the entire notemap file
  2. As UC1 is not done very frequently, it is acceptable that it requires several extra steps relative to UC2
  3. UC2 should be achievable with as few steps/keystrokes as possible
  4. Given that there is no screen/display to provide user feedback, UC2 should be as memorable as possible

With that perspective, your 3rd proposal is an improvement in most fronts:

  1. With your approach, a persistent transposition change would only require modifying one command line argument in haxo.service (e.g. --transposition -2) instead of (1) the full contents of the notemap file and (2) the path argument (--notemap-file) to haxo.service
  2. Changing temporary transposition would require exactly three steps:
    1. Low B + draw air for 1s to enter Transpose Mode
    2. Blow fingering to pick the concert note (e.g. concert Eb) that will be the new C (hear the new C)
    3. Press all 3 left-palm keys together to exit Transpose Mode
  3. One thing I wish we could improve is "memorability". I would like to find some mnemonic rule to associate low B with Transpose. Since this might not be a frequently use feature, I can imagine people forgetting (i.e "Was it B, Bb, C#?"). But maybe someone out there can propose a mnemonic or even a different fingering to enter Transpose Mode (Oh, I just realized that in English solfeggio, "B" is "Ti", so that might work as a good mnemonic. "Transpose" -> Starts with "T" -> Pronounce "Ti" -> "...sol-la-Ti-do" -> "B"... thoughts?)
  4. Regarding your breaking change concern, I do not think it should stop you. This is a relatively new project, I don't anticipate a lot of people out there are running their own notemaps. That said, I agree that you should try to implement this in a way that their customized notemap does not crash the updated executable 👍
petermoz commented 6 months ago

Thanks Javier, started a discussion here: https://github.com/cardonabits/haxo-hw/discussions/60

I don't have any better ideas about the mnemonic (and never learnt solfeggio)

petermoz commented 5 months ago

I've got the first step working in a branch here: https://github.com/petermoz/haxo-rs/tree/transpose

In that, I changed the notemap to be in concert pitch (added 14 to everything), and then added the --transpose CLI option (default to -14). So you can get an alto by changing to -9, bari with -21 etc. The impl details were pretty easy: I added a transpose offset to notemap.rs which is set from the CLI (and could be changed dynamically).

I'll still give some time for feedback on the discussion before trying to merge anything

jcard0na commented 5 months ago

I'm on the road for the next 24 hours, with limited laptop time, but I'll certainly take a look at this after that.
One quick question, though... If you switch from alto to bari sound in the pre-installed sound font, I think the sound is transposed by an octave already (based on my recollection, can't confirm right now). But if that is the case, wouldn't we just need to pick the relative transposition, without picking the octave? i.e. couldn't we just use --transpose -2 for all Bb instruments and --transpose 3 for all Eb, letting the soundfont pick the right octave? I suppose I need to dig deeper, just floating the question now in case that is something you had already ruled out before deciding we need -14, -9, -21, etc. values.

Thanks!

petermoz commented 5 months ago

I tried out the different patches in the sound font and I'm pretty convinced there's no octave transposition going on. i.e. you hear exactly the same octave when playing the bari or alto patches. That makes sense to me, when sending a given pitch via MIDI you'd expect to hear the same octave regardless of what instrument is playing it.

So I'm pretty sure you need the separate -9 / -21 for alto / bari and -2 / -14 for soprano / tenor. Open to be convinced otherwise though, let me know what you think once you've had time to try (no rush)!

jcard0na commented 5 months ago

OK, back home, with some time to look at this. Yes, you are right, I just confirmed that the octaves do not change between instruments, just the timbre. My memory failed me. So 👍 for the -2, -9, ... proposal.

I also looked at the code and it looks clean and straightforward. Don't feel you need a lot more feedback before pushing your changes... so far you have unanimous support for your proposed approach! 💪

jcard0na commented 5 months ago

BTW, just a question that hit me as I was retiring for the day... In temporary transposition, how would one distinguish between alto and bari? Using the octave key? (i.e. low Eb for bari, mid Eb for alto (i.e. octave key), and even high Eb for sopranino (i.e. palm Eb)?) Sounds like the most intuitive approach, but wanted to confirm if that is what you had in mind as well.

petermoz commented 5 months ago

Hi Javier, thanks for confirming -2, -9 etc.

I have a draft of some code that implements the "transpose mode" here: https://github.com/petermoz/haxo-rs/tree/transpose-mode It's not ready for a code review yet, but it works for testing out the concept.

Per the discussion above, play Low B and draw in air to enter transpose mode (haxo will do a double beep), then press appropriate keys and blow air to set the transposition (haxo will play concert "high C" and transposed "high C"), then the three left palm keys to exit.

Regarding the Low Eb for bari etc, yes that's exactly what I've done. "High C" is treated as the reference pitch, and then the transpose offset is set based on distance from the reference pitch. So playing a "High Bb" will set the tranpose to -2 (soprano), "Mid Eb" will give you alto, "Mid Bb" tenor and "Low Eb" bari. To set the haxophone to produce notes at concert pitch, you just play the reference pitch ("High C")

The one thing I don't like about this approach is that to achieve the full range of soprano to bari, it means the reference pitch has to be quite high (High C). So if your reason to use the transpose function is not to emulate a different kind of saxophone, but it's because you want to e.g. "put this chart up 1 whole step" then it feels a little weird that the reference is so high.

This makes me think that haxo might eventually end up with two different types of "transpose mode" - one for setting the type of saxophone, and then another one for nudging the pitch up or down one semitone at a time (like your original suggestion) that would be used when you want to "transpose this chart to a different key"

For now, though, I think this is a step in the right direction, so I think we should continue with this approach.

I'd probably separate the code into at least a separate function before doing a PR, but I'd be interested in feedback about the actual mechanism (e.g. which notes to "beep", what pressure to use as a play note threshold, etc). So please try my branch and let me know what you think!

Thanks Peter

jcard0na commented 5 months ago

Hi @petermoz

I just tried your branch. I did not look at the code, just trying to get the user experience. And it is great!

Things that I tried:

  1. Select transposition. Loved the two beeps, which make it clear that we are in transposing instead of (single beep) changing instruments.
  2. Selected the extreme notes (high F#, low Bb). It brought tears to my eyes hearing notes on the haxophone that I had never heard before... :smiling_face_with_tear:
  3. Tried transposition with MIDI. Works flawlessly.

Regarding:

This makes me think that haxo might eventually end up with two different types of "transpose mode" - one for setting the type of saxophone, and then another one for nudging the pitch up or down one semitone at a time (like your original suggestion) that would be used when you want to "transpose this chart to a different key"

To me the simplest approach could be to use R1/R3 keys without requiring to blow a note (like for changing instruments) while in the current transpose mode. So, in transpose mode, one could:

  1. Finger a valid note and blow (what you have now)
  2. Hit R1 or R3 without blowing for +1/2 | -1/2 tone "fine" transposition. Same double beep feedback as 1.

Implementing both in transpose mode should be feasible because R1 nor R3 alone are valid fingerings. In the remote possibility that someone defines those as fingerings on their notemap.json, their definition would take precedence and shadow (disable) the "fine" transposition key. So, "fine" transposition would only be active if neither standalone R1 nor standalone R3 are defined in the notemap.

Probably the biggest risk I see is that while releasing a valid note, one could fleetingly press R1 or R3 and throw the transposition off by 1/2 a tone. Definitely not something one wants to experience live... :blush: To prevent that, we would need to require a minimum press duration on those.

What do you think?

At any rate, I agree that fine tuning it can be implemented later, at your discretion. What you have now is a big improvement already. Great work!

petermoz commented 5 months ago

Thanks Javier, "fine transpose" seems like a good idea. I'll have a look at that before submitting a PR for the transpose mode. In the meantime, I think it's worth submitting just the first part (notemap changes and --transpose argument). So I'll send a PR now

Ekolide commented 5 months ago

Hi I'd like to offer some thoughts on "fine transpose" from the EWI world. It's not uncommon, rather it's a feature, for EWI fingerings to use the palm keys and left hand pinky keys for instant transposing +1or -1. Let's say on the Haxophone I have set R2 to be +1 and R3 to be -1, I can use any standard fingering on the notemap, and when I use any fingering in combination with R2 (and only in combination with R2), the Haxophone would send the intended MIDI note +1. The same, but -1 for R3. There is no feedback other than the fact that the user chose to hit that key, and the resulting sound. This should maybe not be enabled by default.

From this perspective, my suggestion for the "fine transpose" on the Haxophone is to use it in a momentary sense. Inside haxo.service the user could define --fine-transpose arguments according to their preferences. I suggest a format in the style of --fine transpose R2:+1 R3:-1 L2:+1 L6:-1. The engine would not necessarily limit the user on how many of these transposing keys can be used, and using one +1 key in combination with another +1 key would result in a "fine transposition" of +2.

There is from this starting point the possibility of giving the user the option if they want to use momentary "fine transpose" or if they want to use it as a latching, incrementing mode - akin to what was originally suggested. That is, when I press a +1 key, the whole instrument transposes up +1 even when I let go of the "fine transpose" key. This could result in a haxo.service argument that looks like --fine-transpose R2:+1:MOM R3:-1:LATCH L2:+1:LATCH L6:-1:MOM or something like it.

Furthering on this, why limit ourselves to +1 and -1? What about something radical like --fine-transpose R0:+12:LATCH and R4:-12:LATCH? Why should the engine limit to only transposing in half steps, could this be arbitrary instead?

As an even further thought ahead, at point I'm dreaming: What if the Haxophone had a small vibration motor? That way, if the user wanted "latching" fine transpose, the instrument could give off a light vibration as feedback. So if the user is performing, this would not be interrupted or disturbed by the instrument beeping. Of course usable for the instrument selection mode and transpose mode as well.

There's a more in-depth article from Bret Pimentel about AKAI EWI fingerings. The rough idea of how something like it can be implemented on the Haxophone can be summarized by this comment. The AKAI EWI fingerings get a bit more specific in it's possibilities and it's basic principles, though.

Would love to hear some thoughts, cheers!

petermoz commented 5 months ago

Hi @Ekolide - all interesting ideas, thanks for the link to the EWI article. Certainly the great thing about the haxophone is that all these things are possible.

For the "momentary" modifiers, it seems like you could achieve this with the notemap.json. The notemap is just defining the mapping from a set of keys to a note, and a "set of keys plus a modifier" is really just "a different set of keys", so this could be mapped directly in the notemap. Obviously it would be painful to have to manually write a notemap that includes "keys" -> "note" and "keys + R2" -> "note + 1" for every set of keys, but you could write script to do that for you.

In any case, these are good ideas, but I don't plan to implement any of that here. I see the transposition for different saxophone types, plus (maybe) the fine transposition as the things I'm trying to achieve in this piece of work, and I'll leave the other modes you described to a future change.

Cheers Peter

Ekolide commented 5 months ago

Hey @petermoz! Thanks for a good and clear answer.

I certainly do not expect to get any or all of that implemented, but rather thought that voicing an adjacent idea is better than not voicing it. My current solution is, as you suggested, using the notemap. It is indeed painful to do it manually, but it's been effective once the notemap has been generated.

For what it's worth, I do think this sort of way of working with "fine transposition" is a serious solution for the problem that has been presented here. Certainly not a perfect solution, and I'd love to get some feedback on improvement, but it would be flexible and user-centric. I understand that I'm not developing the solution, of course the final say goes to whoever does that.

Thanks!