Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make specifying time consistent across time(), length() and similar functions #71

Open
marcora opened this issue Jan 26, 2021 · 6 comments
Labels
question Further information is requested

Comments

@marcora
Copy link

marcora commented Jan 26, 2021

Currently, the way time argument is interpreted is not consistent across functions that use time as an argument, for example:

time(4) -> 4 measures worth of time
length(4) -> 4 milliseconds

I would suggest to make an integer or fraction represent a multiple/subdivision of a measure and add a time unit specificier if other measures of time are desired, for example:

length(4) -> 4 measures worth of time
length(4ms) -> 4 milliseconds
length(4s) -> 4 seconds

this could be extended to other values, for which there is a default unit, but different units could be specified, for example:

fx(filter low 4) -> filter cutoff at 4 Hz
fx(filter low 4kHz) -> filter cutoff at 4000 Hz or fx(filter low 4k) to save typing

@tmhglnd
Copy link
Owner

tmhglnd commented Jan 26, 2021

Thanks!
So a few things that come to mind for now:

  • There are a few functions that use time, for some my preference is division units, for others milliseconds
  • I would like for the prefered units to be the easiest to type
  • This would still result in some inconsistencies, so we might also need a unit for divisions

Examples

I like time in division (since this is musically important and makes sense)

time(1/4) time(3) time(5/16 1/8)

I like envelopes in milliseconds, because this does not change in length when tempo is changed, and is usually the default time-unit with other synthesizers/software and midi-instruments

shape(5 100) shape(170 15000) length(500)

Possible Solution

So I'm not yet convinced I want to break those conventions, but an intermediate solution could be to work with units for all if you want to make sure the right unit is used.

So for example:

length(4m) -> 4 measures
length(4ms) -> 4 milliseconds
length(4s) -> 4 seconds

or instead of the m for measures some other suggestions I have are to work with / or : instead resulting in:

length(4/) -> 4 measures
length(4:) -> 4 measures

equally valid would then be:

length(3/8)
length(3:8)

another idea could be to interpret floating-points as milliseconds

length(4.) -> 4 milliseconds

This currently does break with the time, because time(0.25) is the same as time(1/4)

@tmhglnd tmhglnd added the question Further information is requested label Jan 26, 2021
@marcora
Copy link
Author

marcora commented Jan 27, 2021

I see your point of having the more sensible unit being the default (without the need to specify it after the value). Now that I thought about it some more I think the problem lies with the fact that note length (which should be, by default, expressed in terms of division units) has been conflated with length of an envelope stage (attack, decay, etc) which should be expressed in ms by default. Perhaps those two concepts should be disambiguated. I haven't experimented with synth parameters much yet, but how do you specify "hold time" (note on->note off in midi terms) with synths? There should a notion of note duration (and perhaps even sample/loop duration, especially if time stretching is enabled) and that should be, by default, expressed in time division units.

@tmhglnd
Copy link
Owner

tmhglnd commented Jan 27, 2021

I've always expressed note-off duration in milliseconds as well, but that is probably because of working with pureData and Max a lot. A piano-roll in a DAW does indeed show the length as a note-timevalue.

For the synth you use the function shape() : https://tmhglnd.github.io/mercury/02-instrument.html#shape

The number of arguments determines if it is default attack+release, attack+release, attack+sustain+release.

@marcora
Copy link
Author

marcora commented Jan 28, 2021

It is difficult to specify the duration of a note (note on -> note off) in classical music notation terms (i.e., time divisions, as in piano roll or, well before that, western music notation) by specifying ADSR params. Sustain is normally a level (not time) and the release time should start after the note is released/note off event is fired. Also, by expressing note duration in ms, its time division value will change if tempo is changed.

For these reasons I think that implementing a note duration parameter as it is done in western music notation/midi makes more sense.

A possible solution, without having to mess around with units etc, would be to reserve length() to set note on/off interval in ms and reserve duration() to set note on/off interval in division units as in time().

I believe these two functions should also be available for synths and samples as well and, when specified, that the adsr envelope (or start/stop sample playback) should be applied over time based on the on/off signal generated using these functions (same as what happened in synthesizers when receiving midi note on/off or cv gate in the modular world, as opposed to cv trigger... which is how time() works in Mercury).

@marcora
Copy link
Author

marcora commented Jan 31, 2021

So I guess what I am saying is to implement the notion of a gate event (rather than just a trigger) everywhere and not only for the midi object. Not sure why most live coding systems I have tried struggle with this concept (see the way Sonic Pi approaches this in the attachment, which is still a suboptimal attempt to make a trigger ("play") work as a gate by having sustain time in the synth expressed as a fraction or multiple of one measure).

I guess what I am advocating for is that the duration of a note event (gate on->off as in midi note on->off) should be an attribute of the "play" call, not of a sound (or sample) definition. This is, in my opinion, the most natural approach given that electronic gear and daw software have used this concept for as long as I can remember.

This way the AD stages (expressed as a number of some unit of time, e.g., ms) are triggered when the gate opens up, then S stage level (expressed as a number of some unit of amplitude) is maintained until the gate closes up, at which point the R stage (also expressed as time) is triggered. Again, envelope and note duration are kept separate!

The gate time could be expressed as ms or division based on calls to different functions (e.g., duration() and length()) or by implementing some sort of unit specifier as you suggested (e.g., 4 for 4ms and 4/ for 4 measures).

Hope this makes sense.

Screen Shot 2021-01-31 at 08 52 39

@marcora
Copy link
Author

marcora commented Jan 31, 2021

I realize that this may be too onerous to implement or something you disagree with from a software design perspective, and that's totally fine. A simple solution would be to have mercury interpret the value of arguments that represet time as ms when no forward slash character or decimal point is present. For example,

4 = 4ms
4/ or 4/1 = 4 measures
4. or 4.0 = 4 measures
1/4 = 1/4 measure
.25 or 0.25 = 1/4 measure (I doubt people would need to specify fractions of a ms)

thus in practice integers are always interpreted as ms and floats (or fractions) always interpreted as a time division

In Sonic Pi time seems to always be expressed time as beats, but I like the idea of having Mercury allowing the flexibility of expressing time in either ms or time division

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants