zardini123 / AnaMark-Tuning-Library

C++ library for interpreting and using AnaMark Tuning files (.tun V0.0 - V2.0), multiple scales files (.msf), and Scala files (.scl and .kbm).
https://www.mark-henning.de/am_downloads_eng.php
MIT License
14 stars 4 forks source link

Collaboration Opportunity? #2

Open baconpaul opened 3 years ago

baconpaul commented 3 years ago

Hi!

Thanks for making this code available.

Over in the surge synth team (the group that supports the open source Surge synthesizer - https://surge-synthesizer.github.io and https://surge-synth-team.org/) we factored the SCL/KBM parser I wrote for Surge into a standalone header only 100% test coverage modern C++ library. https://github.com/surge-synthesizer/tuning-library

We've used this library to make our tuning workbench synth, to add SCL/KBM support to dexed, and with surge 1.7.0 shipping Aug 1, to run the surge tuning.

The TUNv2 spec is enormous. I didn't want to try and support it. But a user pointed us today to this repo which contains the code.

I'm wondering if you would be interested in collaborating perhaps? Namely, take this TUN code and bring it into the tuning-library API format (so support the minimal client api, make it header only, etc... immediately 3 synths would get supported), run it against our test suite, etc....

I totally understand if that's not something you are interested in. And since you have kindly released your implementation under a very open license (which we also did with our lib - I picked MIT in purpose for the sub-lib even though Surge and Dexed which consume it are GPL3) I may allso just take a crack at it early this fall. But wanted to drop a note in any case!

Best

Paul

zardini123 commented 3 years ago

Hi @baconpaul! I really appreciate you reaching out about this!

One clarification I'd like to make before I respond is that I am not Mark Henning. I contacted Mark a couple months ago to be able to make Mark's library (which was originally available as a .zip on his website) a nice little repository (which is what you see here). It was Mark's decision to make it MIT, which I personally am extremely grateful for.

The code you see in the repo today is not too far away from what Mark originally wrote that was distributed in the .zip since 2009. I think he really had some good ideas about implementation that makes the library still applicable and usable without any changes, eleven years later.

Fun thing to note is that the API you all wrote for tuning-library is pretty similar to that of Mark's. For example you have a object that describes a single scale (your class Tuning, Mark's class CSingleScale). You both have functions for reading from a stream. Correct me if I'm wrong, but it seems tuning-library only has SCL/KBM read support, which is the same level of support for SCL/KBM as Mark's library. From that, we both don't have SCL/KBM write support, which I hope to add some day. You guys do have some nice utility functions for quick retuning that I don't remember seeing in Marks (but there is a reason why Mark's doesn't have many functions like that currently, and I'd have to explain on a separate occasion why, but a hint is because of TUN_Formula.h).

You got the right idea that TUNv2 spec is crazy. To be able to support all possible TUN files is certainly not a 24 hour job (nor a week job), and one could guess this is why this library exists. Another clarification I'd like to make is that this is not just a TUN v2.0 library. It is capable of reading and writing TUN v0.0 - v2.0 (which includes v1.0). This also support MSF (multi scale tuning), which is an amazing feature for some certainly crazy xenharmonic music.

My thoughts about the best course of action for everyone (for you and for fellow programmers wanting to have all possible tuning file support) would be to begin a very slow migration from tuning-library to this library, once a good deal of collaboration has been executed. With my statement about similarities in mind, Mark's current API for SCL/KBM is very similar to yours, which hits that checkbox for minimal API (for SCL/KBM at least). You have some cool ideas on what a tuning library should be, including being header only. A header-only paradigm could certainly help this libraries structure, as code is currently placed in odd ratios between .cpp and .h. You have some great tests for SCL/KBM already (yay catch2!) so much of that code could be transferred over easily. Some day in the future, I'd like to have tests for TUN and the sub functions as well.

So in other words, instead of doing Anamark-Tuning-Library --> tuning-library, I recommend tuning-library --> Anamark-Tuning-Library. This change would be to ensure a quality library for TUN (v0.0 - v2.0), SCL, and KBM for years to come. From what I can tell, Mark didn't just write this library for parsing files, but a centralized library for xenharmonic and microtonal music. Of course I do not want to break any changes in API that your users use for your library, so during this migration, we could write a wrapper file of sorts for your repo to ensure no immediate breaking changes.

Once we figure out details, I'd very much like to bring Mark on board to ensure his voice is heard with his own work.

Conclusion: Yes, let's collaborate! But see all above :)

Thank you, Taras Palczynski

baconpaul commented 3 years ago

Yeah that's reasonable. There are some features of tuning-library I like (in order: header only, 100% test coverage, integrated with several synths already) which would be work to preserve and port. There's also a couple of really important things which it seems our library does - most notably handle midi notes from -256 to +256 - which are really key to make surge work.

But I had indeed noticed the similar API when I read this.

And we wrote tuning-library exactly for the reason you share namely a centralized MIT licensed library for microtonal music which implemented the full spec. And I have no particular pride about which direction we go (although would welcome you if you wanted to consolidate the effort for the final library with the rest of the folks in the surge synth team who have worked on our tuning apps - of course we can work that out some other time).

So tell you what: I'm still getting ready to ship Surge 1.7.0. Let me take a look at this once we have that released in August. I am glad you are amenable. I think we really do need a single library to do this if we want commercial devs to support more robust tuning.

Thank you!

zardini123 commented 3 years ago

Yeah, get back to me once your complete with your update! This library certainly has the most complete implementation of the more popular scale types, so having you and your team expand and test this one would be the best time-to-results ratio. Just needs some tweaks, small QOL additions, and coverage.

There are some features of tuning-library I like (in order: header only, 100% test coverage, integrated with several synths already) which would be work to preserve and port

Yeah though with TUN there is so much to account for so naturally coverage is gonna be time consuming. I think for SCL/KBM support in this repo its like less than 5% of the total codebase (I don't know, just guessing).

Of course if we ever find that header only is not going to work with the complexity, your team could always fork our centralized library and make something to compile everything into a single header. Thats certainly something to discuss at a later date.

There's also a couple of really important things which it seems our library does - most notably handle midi notes from -256 to +256 - which are really key to make surge work.

Yes! This is a change I wanted to make to Mark's library, but in a slightly different degree. Due to how most synths map midi note to scale note, Mark made the size of the scale frequencies constant at 128, which is very poor for very tight tunings. There is a technical reason why this is especially because of how the TUNv2 spec works, so implementation certainly would differ. So in some ways allowing "infinite" number of scale notes, but then mapping the midi notes to a small range (or any range), would allow -256 to +256. Would certainly allow versatility, nevertheless.

Let me know the other important things when you get the chance.

Thanks!

baconpaul commented 3 years ago

yeah the outside-128 thing away from scl/kbm is tough. Things like Tunv1 and MTS give you the keys but don't give you a concept of a 'scale length' so you can't repeat.

but lemme poke at the code after our release. I'm just happy you are interested in potentially working together. The lack of a 'once and well' library for tuning motivated our work in our library; and I think it remains important.

zardini123 commented 3 years ago

yeah the outside-128 thing away from scl/kbm is tough. Things like Tunv1 and MTS give you the keys but don't give you a concept of a 'scale length' so you can't repeat.

Yeah that's what I thought. Do you have -256 +256 implemented in yours, or was that more of a hypothetical?

baconpaul commented 3 years ago

We have it in ours and need it for surge

AnaMarkH commented 3 years ago

Hi @baconpaul!

Zardini123 asked me to join this discussion, as the GitHub repo is based on my .TUN specs and my library to handle these files.

I support the idea of collaboration between both projects to get a versatile library and to make it as easy as possible to handle micro tuning for any synth developer.

Putting all code in header files seems reasonable. Should be a diligent but routine piece of work for the .TUN lib.

As for the MIDI note numbers ranging from -256 to +256:

Just for my understanding. The standard 128 MIDI notes cover a range of 10 octaves + 8 semitones. The range -256 to +256 is meant to cover the same pitch range (having 1/8 tones instead of semitones), isn't it? Or is it indeed intended to cover 42 octaves?

Anyway, it should be possible to enhance the .TUN spec from 0 to 128 to the extended range from -256 to +256. The algorithms can be applied to that range. One has to care for the extended array bounds, loop start/end, read checks and so on.

It is necessary to touch the file format, however. This is a bit tricky, because in my view, downwards compatibility is a major task and files with this extended range shall be readable by already existing synths. I can make a suggestion for a .TUN V2.1 spec which considers that range. However this may take a while because I will be on a vacation trip soon.

baconpaul commented 3 years ago

Hi! Yeah the intent is to cover 42 octaves. (Practically we don't cover that much).

Short version of why is: Surge has flexible modulation all over the place and models all of its pitches in keyboard space. So applying a very broad LFO to a note can easily push you off the end of the midi keyboard. It is rare we go beyond -50 or +200 but it is frequent to go beyond 0 and 128. Before I added microtonal support to surge it had an internal table which was (basically) 2^x from -256 to 256 which it used and then multiplied by 8.167etc... to get to frequency.

AnaMarkH commented 3 years ago

@baconpaul: Is this a common standard or is that a surge-specific feature? Do common VST hosts support the MIDI note range -256 to 256?

baconpaul commented 3 years ago

So the external midi range of all VIs is 0...128 (I don't know if MIDI 2 will change that but lets leave that be).

But think about it like this. Imagine you set pitchbend range to +/-48 and play note 127 and bend all the way up. And imagine you are in a very "tight" tuning (like something extreme like ED2-96). That bend up will only cover half an octave of frequency but will internally require you to be able to resolve the note of inbound midi note [0,128] plus bend span. That's basically what surge (and what our implementation in dexed) has to do. So "in the presence of modulators, instruments generate notes which are outside the midi range" seems like a relatively common use case. But I haven't been inside the code of that many VIs.

I think the real trick isn't to extend beyond 0,128 as much as it is to have the spec give the periodicity of the scale. With TUN we could just assume a periodicity of 128 if we had to, but the thing I really liked about SCL/KBM is that the scale periodicity and the keyboard assignment are separated, so you can logically think about instruments with much bigger keyboards.

AnaMarkH commented 3 years ago

By the way, in .TUN, the periodicity of a scale can be set to any value you like. If you e.g. have a periodicity of 7 notes, just define note 0 .. 7, whereas notes 0 .. 6 contain the tuning and the value of note 7 specifies the periodicity. See the .TUN spec PDF section 3.6.1.

.TUN also includes a separate keyboard mapping. See the .TUN spec PDF section 3.8.

AnaMarkH commented 3 years ago

BTW^2: Keyboard mapping requires a functional tuning section. This is the most versatile way of tuning definition.

baconpaul commented 3 years ago

Yes sorry I didn't mean to imply that TUN couldn't do that (I have only looked briefly at TUNv2 and realized it was "too big to implement" which is why i was excited to find this lib)!

AnaMarkH commented 3 years ago

The idea behind TUNv2 was the following:

It is the same thing as with video encoders/decoders: The encoder does most of the work to allow easy implementation of the decoders for fast video playback.

baconpaul commented 3 years ago

Yeah! I meant "too big for me to independently implement from the PDF on your website" not "too big for you to implement" - which is why I'm excited that the implementation lib is opensource (with, it seems, the same goal we had in surge land with making our SCL/KBM interpreter open source).

BTW, if you get a chance take a look at our tuning workbench synth (https://surge-synth-team.org/ has it) which lets you do real time SCL/KBM edits while playing the synth. It's been handy way for us to think about tunings. But again in SCL/KBM format.

zardini123 commented 3 years ago

It is the same thing as with video encoders/decoders: The encoder does most of the work to allow easy implementation of the decoders for fast video playback.

I'm having some issues understanding how this metaphor applies to the TUN library, especially with that when it comes to MIDI note range.

Currently the midi note range of [0, 127] is hard coded. Variable MaxNumOfNotes in TUN_Scale.cpp is used in function Apply in class CFormula to exit early if the scale note index is in the bound [0, MaxNumOfNotes). If a user needed something other than that (i.e. [-256, 256], they would have to manually edit the Apply function, and ensure any other function that deals with bounds is updated. Of course the current functionality follows section 3.6.1, but to me seems like an unfortunate hurdle. Providing an API to the user to set these bounds to whatever they desire would be perfect.

I honestly think a TUN v2.1 spec with any definable bound (not just -256 +256) would be a great addition to an already powerful functional tuning system. With forwards and backwards compatibility in mind, TUN v2 and SCL would both benefit from it (just tile the scale), but TUN <v2 and MTS are stuck to [0,127]. Providing proper information, returns, and/or errors with functions to set the bounds would ensure TUN <v2 and MTS stay to their original spec, but allow the tillable scales to extend to infinity and beyond.

zardini123 commented 3 years ago

Its certainly a good question about if other VST's already attempt a -256 +256 approach, or if surge is the only one.

For my project Xen MIDI Retuner I need a variable bound other than [0,127] because my plugin works in frequency space, not MIDI space. My plugin calculates the frequency of the current midi note and pitch bend (assuming A=440, equal temp), finds the closest frequency that's in the scale provided, then outputs new midi notes and pitch bends to have the output midi produce a frequency that is that scale's frequency. This is great for users with perfect pitch (or good pitch) who have been taught with the chromatic piano system. A better explanation of how it works is provided in the Readme.

Problem is that due to only having 128 scale notes, tight scales like 43 EDO only span like 3 octaves. Therefore only 3 octaves of the user's input snaps to usable pitches.

I was imagining in the future library, one could define any bound for scale notes, for sake of argument -1205 to 5304. Calculation of the scale could be done in batches, and/or cached, so that it doesn't actually have to calculate (and store) 7000 scale notes, but calculates a single period (plus the outliers) and puts all accesses in that range (and multiples proper periodic constant (i.e. 2 for octave) to ensure its still periodic). There could be some utility functions for others working in frequency space to see when the scale starts exiting a usable frequency range (i.e. from less than Epsilon, to greater than half of sampling rate). Of course there is some edge cases of if the scale was a single note that never increases or decreases in frequency. This paragraph was simply in act of fostering thoughts and ideas for v2.1.

baconpaul commented 3 years ago

Interesting convo

Yeah surge and dexed both resolve midi note to frequency “late” and if you think about the mpe spec many others would also (mpe uses channel pitch bend with a very different granularity but is really just sending note concepts).

So I guess it depends on what we think a tuning is! I always thought of it as a function from a continuous key number to a frequency. That key number has some Calibrating conventions (“middle c” is key 60 on halberstadt layouts say) but as controllers and protocols change it’s not clear to me that it has a natural upper or lower bound.

zardini123 commented 3 years ago

I always thought of [tuning] as a function from a continuous key number to a frequency.

Thinking of tuning as a function from a continuous key number to a frequency is certainly interesting, but I think is a bit odd. Pianos and keyboards have a direct mapping from key (midi note) to scale note. The layout of the keys repeats every 12 notes, just like the scale. This mapping is discrete, not continuous.

Stringed instruments with no frets have the ability to not follow any scale if the player chooses, though one could assume they will fall into some sort of scale eventually because we humans fancy intervals (which scales inherit). Pitchbend in MIDI is used to bend between scale notes (usually for an ornamental sake), and could follow a similar argument to the string instrument argument. Scales/tunings themselves do not have a property that states "scale note 12.4 equals this pitch."

For surge, are you just sampling the current midi note, the next midi note, and using pitchbend as an interpolator between the two?

With that, I wouldn't correlate scales/tunings to continuous because of this interpolation. That interpolation is not a property of scales, and more of an ornament of natural music playing. Wikipedia considers scales to be "any set of musical notes ordered by fundamental frequency or pitch". The Oxford dictionary considers scales as "an arrangement of the notes in any system of music in ascending or descending order of pitch." Though this is now more of an argument about "is a set/arrangement continuous," but I think an everyman would consider an arrangement of flowers to not be 3.2 flowers.

So in other words, scales got x number of frequencies (where x is an positive integer) per scale period. TUN v2 has a way to change a scale note's tuning at any period. Does the entire range of scale notes until the change happens counts as a single scale? Or is it a scale repeated with changes?

127 for midi notes exists because each byte of midi had a status bit, and seven data bits. I remember reading/hearing somewhere that the creators of MIDI were wanting to have much broader capabilities (i.e. higher baud rate meaning more data), but it would of made the hardware very expensive. Because hardware is cheap and software is super capable, I bet folks at MMA were like " 'bout time we make this upgrade" especially cuz of the rise of MPE and microtonal. It'd be fun to see if MIDI 2 is providing support for midi notes outside the range of [0, 127].

AnaMarkH commented 3 years ago

Ok, it seems that we are touching music theory now.

Indeed, there is no need to limit the note range to -256 .. 256. We could also chose -32767 .. +32768. Neither memory nor disc space nor read/write/calculation time is critical in such dimensions. But does it make sense? How many notes are reasonably usable? As for the 43 EDO tuning: -256 .. 256 would span 12 octaves.

Instead, the continous midi note number could be an alternative. Using e.g. aftertouch, one could simulate a note-specific pitch-bend while playing polyphonic. To have 1/8 notes, one could play the melody line with the right hand while the left uses a MIDI controller which sends specific aftertouch events.

And what about MIDI sysex tuning messages? My synth AnaMark has an implementation for that, and this is some kind which is standardized since decades. If we try to create a "complete" tuning lib, we should consider this as well.

As for the tuning workbench: Very interesting. If you implement TUNv2, you could also provide an interface using the functional tuning possibilities. This could give it a break-through as it would make it explorable.

So, before getting lost in too many details: What would you think should be the next steps?

baconpaul commented 3 years ago

Right yes! I think we all agree and can all find points to disagree about needlessly. Next steps is the right idea

So my next step is ship surge 1.7.0 on August 1. Once that’s done there’s a few things we could do

  1. Does our test suite pass with your library on our scl/kbm set? If so that’s a good sign. Can we port those tests to this repo? I would pull some of my surge collaborators into this effort too.

  2. What functions are in tuning lib which are not in your lib? Let’s identify them. I have some utility functions to creat mappings and scales which are useful as you mentioned. And of course extended range

  3. If we know those functions and api points And the tests all pass can we gut our tuning lib for yours?

  4. Then one of us codes up mts and declares some combination of victory and tuning completeness?

That’s what I would do first

And yes you exactly get the point of tws. Put the tuning modeling front and center in a synth in your daw. Having it support tun too would be great.

Thoughts? If so we could get cracking in the late summer / early autumn!

zardini123 commented 3 years ago

@baconpaul is the tuning workbench part of the UI of surge?

  1. If we know those functions and api points And the tests all pass can we gut our tuning lib for yours?

Whatever is best for your project!

  1. What functions are in tuning lib which are not in your lib? Let’s identify them. I have some utility functions to creat mappings and scales which are useful as you mentioned.

The only utility functions that this lib has is all in CSingleScale. Those functions being Reset (Set scale to 12 EDO), InitEqual, and ResetKeyboardMapping. Oddly enough, none of these functions, nor the SCL/KBM import, deal with the CFormulas that are used by TUN v2 to calculate frequency. I think for forward compatibility sake (especially with the larger range request) we port those functions to use the functional tuning equations.

So therefore I think this would be the best roadmap (very similar to yours, Paul):

  1. Port already existing scale utility functions to use CFormula.
  2. Port utility functions that exist in tuning-library into here (which should be straight forward due to using CFormula).
  3. Port unit tests from tuning-library (include testing scl/kbm set from tuning-library).
  4. Begin work on extended range. Will probably require creating a new spec (and ensuring backwards compatibility). Not a quick project (probably).
  5. MTS support before or after extended range?
  6. Unit tests for everything related to TUN v2.
baconpaul commented 3 years ago

@baconpaul is the tuning workbench part of the UI of surge?

Nope - a separate synth using the same lib. https://surge-synth-team.org/tuning-workbench-synth/ Similarly dexed has merged our branch which imports our tuning lib (and adds MPE pitch bend support). So 3 synths using our library right now.

And your path sounds correct. I will point out that extended range for SCL/KBM is way easy so we could simply add a flag on the resulting scale supportsExtended and roll it out bit by bit also. And yes my utility functions are trivially small so the pros should be easy!

I used 'catch2' for C++ testing and it worked like a charm, by the way. So I would probably add a step 0 to both our plans along the lines of "move all the source to a src/ and include/ directory; add LICENSE file; add a libs directory with catch2 in it; add a CMakeFile to build a test". I'm happy to do that if you want.

Oh also we run on linux intel, linux arm, mac 64 bit and win 32 and 64 bit, so would need to make sure we can build and test in those situations.

baconpaul commented 3 years ago

Oh and

Pianos and keyboards have a direct mapping from key (midi note) to scale note. The layout of the keys repeats every 12 notes, just like the scale. This mapping is discrete, not continuous.

the reason I started working on surge is because I got a roll seaboard 49, which I described to my friends as "a fretless piano" :)

Where to put the inter-note interpolation we can figure out. Just we will need to interpolate somewhere to work in the VIs I've been working with.

zardini123 commented 3 years ago

the reason I started working on surge is because I got a roll seaboard 49, which I described to my friends as "a fretless piano" :)

Actually the reason I begun working on my Xen MIDI Retuner is because I got a Seaboard. Funny how we both had ideas for it.

Where to put the inter-note interpolation we can figure out. Just we will need to interpolate somewhere to work in the VIs I've been working with.

What dimension of interpolation are you using for surge? Interpolating by cents? Interpolating by hertz? Linearly interpolating between the two ratios?

My earlier statement about how scales don't give enough info about what scale note 3.4 is is simply that, an assumption. The assumption I made for Xen MIDI Retuner is it will linearly interpolate using cents. This is fine for EDO, but some scales that have nice ratios (just/pythag/boilhen-pierce) may not be so nice anymore when 0.5 between two of the scale notes of nice ratios does not make a nice ratio. At a value of 0.5 we essentially have to smart-subdivide the scale. Unless we add to the TUN v2.1 spec a place to define how scales should be interpolated for pitch bend (i.e. be interpreted continuously), we should figure out the most accurate way to interpolate for a scale given.

zardini123 commented 3 years ago

I will point out that extended range for SCL/KBM is way easy so we could simply add a flag on the resulting scale supportsExtended and roll it out bit by bit also.

Yeah though like I said earlier, much of TUN v2 has [0, 127] hardcoded, so hunting down all the functions, changing/adding to the API a bit, and ensuring they don't explode is going to be a job even without making a new spec.

Oh also we run on linux intel, linux arm, mac 64 bit and win 32 and 64 bit, so would need to make sure we can build and test in those situations.

I'm liking the size of that list! Do you have a way to automate building/testing for all those architectures, or is it just telling fellow devs to run it on their systems?

add LICENSE file

Whoops actually I'm gonna add that right now.

I'm happy to do that if you want.

Sure, sounds good!

baconpaul commented 3 years ago

We use free Microsoft azure pipelines to build surge in Linux With gac 7 and 9 Mac windows with vs2017 32 and 64 but and vs2019 64 but and (as of a few days ago cross compiled) arm with every commit yes; and to automate the distribution of our binary. For the tuning library every commit and every pr builds the code and every pr runs all the tests before we merge. Easy to set up. I have done the same for rack and loads of folks copied my setup there too.

baconpaul commented 3 years ago

Your interpolation question is spot on For surge yeah I just linearly interpolate in frequency space and that’s ok. I thought about interpolating in log frequency space for exactly the reason you suggest or pre sampling a finer table in log space for more accurate interpolation

What I would really like to do is to have the interpolation as a trait or runtime option. I have not done that yet but we have a few interesting cases - especially with non monotonic mappings - where that might help. But I’ve not given any deep design thought to this yet

zardini123 commented 3 years ago

We use free Microsoft azure pipelines to build surge in Linux With gac 7 and 9 Mac windows with vs2017 32 and 64 but and vs2019 64 but and (as of a few days ago cross compiled) arm with every commit yes; and to automate the distribution of our binary. For the tuning library every commit and every pr builds the code and every pr runs all the tests before we merge. Easy to set up. I have done the same for rack and loads of folks copied my setup there too.

Is there a specific reason you all use Microsoft Azure Pipelines? I know many open source projects on GitHub use Travis CLI, and to be frank I have never have seen Azure Pipelines used. I've heard that Travis has many great options for different environments (not sure if it has windows 64 and 32, but it has everything else). Travis is especially great being able to embed a badge that takes you straight to the public build information. I know there is some other options like CircleCI and Jenkins. Regardless whatever the CLI is, I'd rather not have it say its "free" but require a card on file, and then charge the crap out of us when we go outside their bounds (instead of just stopping what we're doing that's not considered free).

For coverage, I've seen Codecov used extensively especially as it has a nice website for visualizing what needs to be covered. We should consider using that for checking coverage (and embedding a badge that provides that information).

AnaMarkH commented 3 years ago

Considering the pitch bend / continuous note thing:

In music, all is about frequency ratios between notes (both sequentially played as melody and played at once as chord). Absolute frequencies usually are meaningless.

Let's consider two notes with base (= integer) note numbers N1 and N2 and functions F(N1,0) and F(N2,0) to calculate the frequency by note number, respectively.

So how can we calculate the frequencies for note numbers (N1,x) and (N2,x), where x=[0;1] denotes the note number fraction. x is the same for both notes.

Let C1 be the difference between N1 and N1+1 in cents. Let C2 be the difference between N2 and N2+1 in cents.

The aim is to find a formula to calculate F(N1,x) and F(N2,x) which

1) In case F(N1) / F(N2) = F(N1+1) / F(N2+1) (i.e. C1=C2): keeps the frequency ratios equal during complete pitch bend.

2) otherwise gives a smooth transformation from ratio F(N1)/F(N2) to F(N1+1)/F(N2+1).

We now try the following function:

F(N+x) = F(N) 2^(x C/1200)

This results in:

F(N1+x) / F(N2+x) = [F(N1) 2^(x C1/1200)] / [F(N2) 2^(x C2/1200)]

Let's consider case 1):

Due to C1=C2 we can write:

F(N1+x) / F(N2+x) = [F(N1) 2^(x C1/1200)] / [F(N2) 2^(x C1/1200)] = F(N1) / F(N2)

This fulfills our requirement.

Now let's consider case 2):

F(N1+x) / F(N2+x) = [F(N1) 2^(x C1/1200)] / [F(N2) 2^(x C1/1200)] = [F(N1) / F(N2)] 2^[(x C1/1200) - (x C2/1200)] = [F(N1) / F(N2)] 2^[(C1-C2) * x/1200]

This fulfills our requirement.

You can try this with linear interpolation in Hertz. It won't work. Not even for 12 EDO.

The remaining question is how to define the pitch bend range. One can define it in cents or in notes. Personally, I think it does not make sense to define it in cents. Because in unequal scales you will frequently end inbetween two notes. However I can imagine that someone will try to play portamento effects in unequal scales and wants to keep the ratios equal. So we could simply implement a switch for "define pitch bend in cents" or "define pitch bend in notes". But this is not a scale property but a song property. So this switch should be placed in the synth, not in the scale definition file. Anyhow, we can provide a function in the API which can deal with both pitch bend methods so that the only thing the synth developer has to do is to implement a switch for the pitch bend method.

baconpaul commented 3 years ago

Is there a specific reason you all use Microsoft Azure Pipelines?

They provide free compute resources to open source projects and integrate really well with GitHub. We've been using them for 2 years and have had almost no failures. Critical to our project working!

I'm sure other things work too. I know folks use travis and like it too.

baconpaul commented 3 years ago

@AnaMarkH very useful. Curious what would happen with non-monotonic mappings - and the 1200 sitting there seems curious to me. I was thinking you were going to extend to something like:

Between F(N) and F(N+1) we want the constraint that F(N+d)/F(N) = F(N+1)/F(N) * (2^(d)-1) or some such (I am not being particularly careful here). That is you interpolate between two notes in some exponential space (since our conception of frequency is in the 2^x space not the x space). This is kind of what I meant by "interpolating log pitch rather than pitch" above.

So far I've kept that out of our library and have made it a client problem though. And you end up with code and comments like this

https://github.com/surge-synthesizer/tuning-workbench-synth/blob/1adf9d86948fd60c3610b0b97bafee83b021cc69/Source/TWSVoice.cpp#L600

Also: yes pitch bend has to be in note space I think. A constraint I have kept in TWS Dexed and Surge is: You set pitchbend to 2 keys, you press a C, you bend all the way up, the pitch you hear is the same as if you press a D. I think keyboardists just expect that even in alternate tunings and that's why we resolve frequency "late".

AnaMarkH commented 3 years ago

The 1200 results from the definition of cents. 1200 cents define an octave. An octave usually is defined as doubling the frequency.

Of course, one can think of another definition of octave (e.g. tripling the frequency instead of doubling) which would then be a scale property. This would change the formula terms to "3^(x * C/1200)" or whatever factor is assigned to the term "octave".

But I think, this does not matter. The reason is that as long as the formula to calculate cents from frequency fits to the formula used to calculate frequency from cents, everything is nice. Then it does not matter whether you define 1200 cents per octave or 100000. It does not matter whether you make an octave double, triple or whatever frequency. But people are used to the 1200 cents = 1 octave thing. So changing the definition of cents may confuse people, therefore I would leave it as it is.

AnaMarkH commented 3 years ago

By the way: When it comes to code & specs development, I can start work at the beginning of August. I think, this fits well with the date of the next Surge release.

However, I am somehow unversed in working with the GitHub space and with the OS independent development utilities you are talking about. So I might need some assistance from you.

I currently have Visual Studio 2017 installed. Is this suited (do I need some special plugin)? Or shall I use something like Eclipse instead?

baconpaul commented 3 years ago

Makes sense. I'm sure we can work something out. Seems your approach is probably same as interpolating linearly in log2(frequency) space also, which was my instinct but which I haven't worked out.

And yeah VS2017 is what we use for most surge on windows. CMake is a project specification tool which works perfectly with VS2017 but will also generate Xcode on mac, make on linux, etc... We have a broad group of homogenous devs working on surge and related stuff using native tools. Dev workflow is super duper important I agree. But easy to discuss what works for you and what doesn't at the time. I will say that I've had great benefits from working completely in the open - the community around surge regularly grabs our most recent builds and tests on all sorts of platforms. But it is a bit unnerving at first.

baconpaul commented 3 years ago

So I had 30 minutes free and always think "show vs tell" is a better example of what I mean.

I forked the project here and did a bit of git mv and added tests and a cmake file

https://github.com/baconpaul/AnaMark-Tuning-Library/tree/test-reorg

so you can

git clone https://github.com/baconpaul/AnaMark-Tuning-Library
cd AnaMark-Tuning-Library
git checkout test-reorg

at this point the src include and tests will be in different folders and you will have a cmake file. You can now do (on windows from a visual studio command prompt)

cmake -Bbuild

and you should get build/AnaMark-Tuning-Library.sln which is a VS solution. (I did this on mac but can test in my win vm later). You can then build that normally.

It will have two targets. anamark-tuning-library and anamark-tuning-test which depends on the library. If you build and run the test it will tell you that 9 assertions passed. You can see the assertions and the TDD code style in test/basics.cpp and tests/scl-kbm.cpp

This is more or less how I structured the tuning-library and it worked really well. You aren't header only but that's OK. But wanted to share in case you felt like looking at the sort of thing I meant.

Of course it's a fork, its 25 minutes work. Happy to throw it out if you hate it etc... just in the spirit of 'concrete draft that has worked well for me in other settings'.

baconpaul commented 3 years ago

(hold of on testing that - some windows gotchas now I try it in my VM - will update here)

zardini123 commented 3 years ago

Yeah the draft looks interesting. Though the paradigm of separating source and header files into different folders for this project I am not a fan of for right now. The library has definition and headers mixed wildly between each the .cpp and .h files, so there is going to be a lot of moving around for no reason. So until we do header(s) only, lets keep everything in a src folder (if we can).

Also, I hope there is a way so that we can have multiple files, but then compile it into a single header file. With the amount of code the library already has, having to code everything via a single file is gonna be pain (my pointer finger is gonna hurt from the amount of scrolling lmao). Also the organization will help. I wonder how catch2 does development because they have 170k lines or something.

baconpaul commented 3 years ago

Yeah a single header is easy.

OK so I did a push and the test now builds and runs win lin mac and arm. Lemme shuffle a bit more!

baconpaul commented 3 years ago

Just pushed some more changes. I moved the .h into the src; made a include/ single file (a synthetic hack for now!) which a client can include and made the test (a client) use that single header. Lemme know what you think about the new version!

zardini123 commented 3 years ago

Lemme know what you think about the new version!

Lovely! Single header including everything is very clean. I have a request:

Can you update the README to have a Build section or something? I'd be very nice for me, Mark, I, and future persons to understand what commands you used to get binaries. Not just that, but also include what binaries are generated (what each binary means/does). Also provide an answer to the question in the section if you able to build for linux, Mac, and windows on the same environment. From what I've seen we can't, and you have to actually have the envioment running to build for it.

zardini123 commented 3 years ago

Also one large goal of this library is also to provide clear documentation on how to use it, and what every part of the library does. With that, we should decide on a C++ documentation generator so that when you/we are learning the library we can begin placing documentation comments that will be used in generation later.

I'd also like to figure out how to documentation coverage, to show the public how much of the documentation is available for the library.

baconpaul commented 3 years ago

I’d be happy of course to update the README. I just didn’t do that before seeing if you hated it or not. Why document something you immediately say “no”to and stuff :)

I’ll find some time for that next day or two and send the changes over as a pull request.

The two documentation systems people seem to like with C++ are doxygen and sphinx - but sphinx requires doxygen as a first step anyway. So I would suggest doxygen as the approach here. I’ve set it up before and can probably do it again.

Finally, if we want an automated build we need to either (1) have you invite CI tools to this repo and set it up or (2) if you want house this repo with the rest of the surge-synthesizer repos (but zero pressure) and I can turn on the pipelines easily for things like PR and doc generation.

baconpaul commented 3 years ago

OK I just pushed an updated version of my branch which has some rudimentary how to build doc; and also includes a doxygen coupling.

if you have doxygen installed on your box (brew install doxygen on mac; sudo apt-get doxygen on linux; and install doxygen.exe from the web on windows) when you build you will also get build/doc/html/index.html which has links to things like this

Screen Shot 2020-07-15 at 9 01 51 PM Screen Shot 2020-07-15 at 9 01 55 PM

baconpaul commented 3 years ago

Oh and if that's interesting to you I can package this up as a pull request so you can merge it here.

zardini123 commented 3 years ago

Yeah ffmpeg and many c libraries uses doxygen, so I think it might be a good choice.

You're more than welcome to create the pull request. I need a couple of days to look CLI's over and get an understanding what systems to use before I accept the request. I also need time to critically look over the request and compile/test things myself.

zardini123 commented 3 years ago

@baconpaul I begun setting up continuous integration for this library via Travis CI (see here https://travis-ci.org/github/zardini123/AnaMark-Tuning-Library). I do not know what arguments are required for builds of windows and linux to complete and then run the test executable, so I request your assistance on that.

Soon I am going to also set up having Travis CI push the documentation to GitHub pages so the docs can be hosted right here on GitHub.

I am also looking into code coverage and how to go about that. I'm still eyeing CodeCov.

baconpaul commented 3 years ago

Looking at the outputs:

  1. It seems on linux you have the command "cmake -B build" vs "cmake -Bbuild" as your first step and that's the error
  2. On windows you are running ./build/anamark-test or whatever and you will need to run ./build/anamark-test.exe

Is the travis configuration in git? If so I can look at that!

zardini123 commented 3 years ago
  1. It seems on linux you have the command "cmake -B build" vs "cmake -Bbuild" as your first step and that's the error

Huh, crazy how a space changes things. Will adjust soon.

Is the travis configuration in git? If so I can look at that!

Yes the config is on git! Everything new is on the develop branch, including your changes. The file name is .travis.yml.

zardini123 commented 3 years ago

Removing the space did not fix the issue. Running ./build/anamark-tuning-test.exe for windows also does not work. :(