markusschloesser / MackieC4_P3

A Mackie C4 Midi Remote Script for Ableton 11
20 stars 2 forks source link

how to bring vpot display modes OTHER than full enlightened to script #98

Closed markusschloesser closed 2 years ago

markusschloesser commented 2 years ago

here's something I could really use your help with: When stuff is mapped through Live we don't need to do anything to have the vpot ring light up properly. However in forward_cc mode we currently only have show_full_enlighted_poti(), which is VPOT_DISPLAY_BOOLEAN and we can just pass [0] or [1] to it. And we do that successfully for a lot of things.

For the new features in Function mode, especially LoopLength and LoopStart I want to indicate the values via different vpot display modes. LoopLength should use VPOT_DISPLAY_SPREAD and LoopStart should use VPOT_DISPLAY_WRAP . Unfortunately I cannot figure this one out.

In consts.py we have: encoder_ring_led_mode_cc_values = {VPOT_DISPLAY_SINGLE_DOT: (0x01, 0x0B), # 01 - 0B VPOT_DISPLAY_BOOST_CUT: (0x11, 0x1B), # 11 - 1B VPOT_DISPLAY_WRAP: (0x21, 0x2B), # 21 - 2B VPOT_DISPLAY_SPREAD: (0x31, 0x36), # 31 - 36 VPOT_DISPLAY_BOOLEAN: (0x20, 0x2B)} # 20 - 2B -- goes to ON in about 6 steps

I am able to tell the script to use VPOT_DISPLAY_SPREAD, but I don't know how access the range of possible values for that vpot display mode and "map" the shown LoopLength to the range.

I tried to replicate what the Live map mode is doing, by adding this to Encoders.py: ` def show_vpot_ring_spread(self, data1): display_mode_cc_base = encoder_ring_led_mode_cc_values[self.__v_pot_display_mode][0] range_end = encoder_ring_led_mode_cc_values[self.v_pot_display_mode][1] - display_mode_cc_base v_pot_display_mode = VPOT_DISPLAY_SPREAD data1 = tuple([display_mode_cc_base + x for x in range(range_end)]) data2 = encoder_ring_led_mode_cc_values[__v_pot_display_mode][data1]

midi CC messages (0xB0, 0x20, data) (CC_STATUS, C4SID_VPOT_CC_ADDRESS_1, data)

    self.send_midi((CC_STATUS, self.__vpot_cc_nbr, data2))`

and calling it in EncoderController.py/on_update_display_timer: elif e.vpot_index() == encoder_14_index: get_loop_length = str(self.song().loop_length) upper_string2 += 'LoopLg ' lower_string2 += adjust_string(get_loop_length, 6) + ' ' data2 = get_loop_length self.__encoders[encoder_15_index].show_vpot_ring_spread(data2) But that either doesn't work and only shows fixed values or it fucks up the script (as well as 25 other variations of that)

Any idea?

BilldarBagdar commented 2 years ago

In consts.py we have: encoder_ring_led_mode_cc_values = {VPOT_DISPLAY_SINGLE_DOT: (0x01, 0x0B), # 01 - 0B VPOT_DISPLAY_BOOST_CUT: (0x11, 0x1B), # 11 - 1B VPOT_DISPLAY_WRAP: (0x21, 0x2B), # 21 - 2B VPOT_DISPLAY_SPREAD: (0x31, 0x36), # 31 - 36 VPOT_DISPLAY_BOOLEAN: (0x20, 0x2B)} # 20 - 2B -- goes to ON in about 6 steps

I wrote those those observational notes based on the interactions I witnessed between the C4 Commander app and the C4 device using MidiOx. So they are accurate in those terms, except I see here the 0x21 - 0x2b Range is mapped twice, for both wrap and boolean.

I seem to recall struggling with this same issue early on. The same techniques should work whether or not we are doing feedback mapping. "Show fully enlightened poti" might need a companion method to "show partially enlightened poti" that does something like the "feedback_rules" variable in Encoders.build_midi_map() method. I mean, we know that feedback mapping works properly, we just need to figure out how to "force poti mappings" manually.

Technically, there's no reason why we couldn't program a short "demo animation" of all the LEDs and LCDs working in unison/harmony that runs after a button push in "user mode".

markusschloesser commented 2 years ago

The same techniques should work whether or not we are doing feedback mapping. "Show fully enlightened poti" might need a companion method to "show partially enlightened poti" that does something like the "feedback_rules" variable in Encoders.build_midi_map() method. I mean, we know that feedback mapping works properly, we just need to figure out how to "force poti mappings" manually.

That's what I am trying to do, unfortunately github doesn't respect indentation when pasting code. This is what we have:

def unlight_vpot_leds(self):
    data2 = encoder_ring_led_mode_cc_values[VPOT_DISPLAY_BOOLEAN][0]
    # midi CC messages (0xB0, 0x20, data) (CC_STATUS, C4SID_VPOT_CC_ADDRESS_1, data)
    self.send_midi((CC_STATUS, self.__vpot_cc_nbr, RING_LED_ALL_OFF))

def show_full_enlighted_poti(self):
    data2 = encoder_ring_led_mode_cc_values[VPOT_DISPLAY_BOOLEAN][1]
    # midi CC messages (0xB0, 0x20, data) (CC_STATUS, C4SID_VPOT_CC_ADDRESS_1, data)
    self.send_midi((CC_STATUS, self.__vpot_cc_nbr, data2))

Here's what I did:

def show_vpot_ring_spread(self, data1):
    display_mode_cc_base = encoder_ring_led_mode_cc_values[self.__v_pot_display_mode][0]
    range_end = encoder_ring_led_mode_cc_values[self.__v_pot_display_mode][1] - display_mode_cc_base
    __v_pot_display_mode = VPOT_DISPLAY_SPREAD
    data1 = tuple([display_mode_cc_base + x for x in range(range_end)])
    data2 = encoder_ring_led_mode_cc_values[__v_pot_display_mode][data1]
    # midi CC messages (0xB0, 0x20, data) (CC_STATUS, C4SID_VPOT_CC_ADDRESS_1, data)
    self.send_midi((CC_STATUS, self.__vpot_cc_nbr, data2))

The first problem is, I don't understand, from a Midi perspective, what is happening. We can only send 3 bytes as midi message, the first is "CC", second is the address of the vpot, but I am struggling with the third. My first intuition was to send "3" (because VPOT_DISPLAY_SPREAD = 3) and then a value within the range of (0x31, 0x36) for the actual spread amount. But that would be 4 midi bytes.

The second problem is how to tell the script that the range of LoopLength should be represented by range of (0x31, 0x36). So in EncoderController.py/on_update_display_timer I have:

show loop length

                elif e.vpot_index() == encoder_14_index:
                    get_loop_length = str(self.song().loop_length)
                    upper_string2 += 'LoopLg '
                    lower_string2 += adjust_string(get_loop_length, 6) + ' '
                    data2 = get_loop_length
                    self.__encoders[encoder_15_index].show_vpot_ring_spread(data2)

which works fine until the line starting with data2. (This code is not yet on GH, as it's not working, only the first 4 lines, but you can it working of you go to Function mode on the second row

markusschloesser commented 2 years ago

ok, I now understand/solved the first problem. I don't need to tell the vpot ring to switch to SPREAD mode, I just need to send values between (0x31, 0x36). I hardcoded one of those value into

def show_vpot_ring_spread(self):
    data2 = encoder_ring_led_mode_cc_values[VPOT_DISPLAY_SPREAD][1]
    # midi CC messages (0xB0, 0x20, data) (CC_STATUS, C4SID_VPOT_CC_ADDRESS_1, data)
    self.send_midi((CC_STATUS, self.__vpot_cc_nbr, 0x34))

and it worked as intended.

So now off the second problem :-)

markusschloesser commented 2 years ago

mmh, just putting in

def show_vpot_ring_spread(self):
    data2 = range(0x31, 0x36)
    # midi CC messages (0xB0, 0x20, data) (CC_STATUS, C4SID_VPOT_CC_ADDRESS_1, data)
    self.send_midi((CC_STATUS, self.__vpot_cc_nbr, data2))

produces a type error with "No registered converter was able to produce a C++ rvalue of type int from this Python object of type range"

markusschloesser commented 2 years ago

mmh

def show_vpot_ring_spread(self):
    data1 = 0x31
    data2 = 0x36 - 0x31
    data3 = tuple(data1 + x for x in range(data2))
    # midi CC messages (0xB0, 0x20, data) (CC_STATUS, C4SID_VPOT_CC_ADDRESS_1, data)
    self.send_midi((CC_STATUS, self.__vpot_cc_nbr, data3))

throws "No registered converter was able to produce a C++ rvalue of type int from this Python object of type tuple" the missing basic python comes back to haunt me πŸ˜‚

BilldarBagdar commented 2 years ago

Correct. After the "display mode" is set, all you need to do is send updated "display mode values" until/unless you change the "display mode".

I just pushed a new branch called demoAnimation up to origin. It's only partially working, but I think it represents a step in the direction we are trying to go... check out User mode. Let me know what you think.

BilldarBagdar commented 2 years ago

Basic MIDI 101 lesson. CC 3 byte messages follow the same pattern as Note On and some other MIDI message types.

The first byte of the tuple represents the "Midi Message Type" and Midi Channel. So a CC message for the first channel is "first byte" 0xB0, and a CC message for the last channel is "first byte" 0xBF (0x0F is always channel 16 of 16). Similarly, a Note On message for the first channel is "first byte" 0x90, and a Note On message for the "last channel" is 0x9F.

The second byte of the tuple represents the CC Number or Note Number. There are 128 of each. Note numbers generally correspond to keyboard keys, 0x00 on the far left hand and 0xFF on the far right hand side facing the keys, but they don't have to. (The C4 uses Note Numbers to represent button presses) CC numbers generally represent all kinds of controls.

The third byte of the tuple represents the value being transmitted to the (CC/Note) control/key on that channel. The "Note value" is also called the "Note velocity". The CC "control value" has the same semantic meaning, the "velocity" with which the control on that channel should be "struck" when this message is processed.

What may be confusing you further is that these C4 LED Modes have a defined reduced range. They only define 10 (11?) steps each, except SPREAD only defines 6 steps:

VPOT_DISPLAY_SINGLE_DOT: (0x01, 0x0B),  # 01 - 0B
VPOT_DISPLAY_BOOST_CUT: (0x11, 0x1B),  # 11 - 1B
VPOT_DISPLAY_WRAP: (0x21, 0x2B),  # 21 - 2B
VPOT_DISPLAY_SPREAD: (0x31, 0x36),  # 31 - 36
VPOT_DISPLAY_BOOLEAN: (0x20, 0x2B)}  # 20 - 2B  -- goes to ON in about 6 steps

In this case we may need to handle the fact that Live is going to send 128 possible values for what we want to interpret as the 6 SPREAD steps for example. Likely need to map one range across the other, to make 6 chunks out of 128 steps, for example.

markusschloesser commented 2 years ago

Just checked out and checked the animations, nice! :-)

Thanks for the MIDI 101, I kind of knew this, but not in that much detail, only learned yesterday that it's always 3 bytes, when Live was complaining about receiving 4 bytes.

Couldn't we simply define functions for all those modes separately? (That's what I tried to do with def show_vpot_ring_spread(self):). I mean we have show_full_enlightened (boolean). So why not just do the other 4 with their respective ranges and then pass the parameter to it? It's not that many forwarded ones, so I would do spread for length, wrap for progress(Song Position pointer), single dot for loop start and boost/cut for crossfader.

markusschloesser commented 2 years ago

In this case we may need to handle the fact that Live is going to send 128 possible values for what we want to interpret as the 6 SPREAD steps for example. Likely need to map one range across the other, to make 6 chunks out of 128 steps, for example.

Wait a sec: is that what you already did in your branch with self.v_pot_display_memory_len = len(self.__v_pot_display_memory[VPOT_CURRENT_CC_VALUE]) ?

Btw how to "map" the X of possible Live values (e.g. Song or Loop Length) to the 6(10) possible values? EDIT: found this https://stackoverflow.com/questions/1969240/mapping-a-range-of-values-to-another

BilldarBagdar commented 2 years ago

Yes, kind of. that self.v_pot_display_memory_len value is just the length of the list of possible values to send to an led ring for that specific ring display mode.

I just pushed another update that kind of fixes the display ring "animation sequence" I was aiming for.

Basically, I suspect you just need to use the length of your small list of "display values" as the modulo against the bigger list of values from Live and take that "modulo remainder" as the index into your list of "display values" to get the actual midi value you want to send to the LED ring.

BilldarBagdar commented 2 years ago

The other direction is more challenging just because you don't have a sweet modulo remainder operation at your service.

If you want to start with the say 10 values and then figure out, for example the first element in my small range of 10 values maps to the first fourteen elements in the larger group, so I need to send my first value fourteen times before I send my second...

EDIT: There are some really good solutions on that SO QA page. I particularly like the one by PaulMcG that is a function that creates other functions (closures in Python?) That technique would be super convenient if project code needed to do more than one specific kind of translation like that. Pass in the two differently sized ranges, get back a translation function from one to the other. (So we could have a dedicated translation function for every different "ring mode" using this technique.)

markusschloesser commented 2 years ago

I just pushed another update that kind of fixes the display ring "animation sequence" I was aiming for.

now that's a proper light show πŸŽ‰πŸ’ƒπŸ‘

found a (probably) old bug with this commit: the first 6 vpot rings in row 0 stay where they were in user mode (lit up). Will try to fix this (when switching assignment mode, do a proper clean-up). UPDATE: this bug ONLY happens in ChanStrip mode

Am I correct to assume, that update_value from def update_led_ring(self, update_value): that you use in animate_v_pot_led_ring would be the "translated" value, coming from Live?

markusschloesser commented 2 years ago

merged your branch into 2022_dev, kicked out my non-working stuff. check Zoom/Scroll :-)

markusschloesser commented 2 years ago

EDIT: There are some really good solutions on that SO QA page. I particularly like the one by PaulMcG that is a function that creates other functions (closures in Python?) That technique would be super convenient if project code needed to do more than one specific kind of translation like that. Pass in the two differently sized ranges, get back a translation function from one to the other. (So we could have a dedicated translation function for every different "ring mode" using this technique.)

def make_interpolater(Live_value_min, Live_value_max, C4_min, C4_max):
    # Figure out how 'wide' each range is
    LiveSpan = Live_value_max - Live_value_min
    C4_Span = C4_max - C4_min

    # Compute the scale factor between left and right values
    scaleFactor = float(C4_Span) / float(LiveSpan)

    # create interpolation function using pre-calculated scaleFactor
    def interp_fn(value):
        return C4_min + (value - Live_value_min) * scaleFactor

    return interp_fn

I added this to MackieC4Components, BUT when trying to use this, always hit a dead end, this time not (yet) because of lack of python knowledge but:

  1. Song/Beat Position Pointer: I need Song.start for Live_value_min, which doesn't exist in the Live Api. Can I just set this to 000:00:00:000 ?
  2. for LoopLength I need to know the minimum Live loop_length, which I don't
  3. for LoopStart, it is basically the same as 1. Any ideas?
BilldarBagdar commented 2 years ago

Am I correct to assume, that update_value from def update_led_ring(self, update_value): that you use in animate_v_pot_led_ring would be the "translated" value, coming from Live?

That update_value has to be a value the LED ring will understand, already translated from whatever range Live offers to the 6 or 10 values the rings understand.

I'm not sure I can answer your other questions without seeing more details, stuff in action. But generally, couldn't you somewhat arbitrarily use 0 for the minimum and 127 for the maximum on Live's side? That's the Midi range.

BilldarBagdar commented 2 years ago

The more I think about a problem like this the more I think a solution may not be completely possible, but IDK. I'm just not seeing a break in the canyon walls. One would think that if Live itself can display that information, then a remote script should also be able to display that same information. But maybe all the various details that go into accurately displaying that data are not exposed by the LOM.

For example, If the Playhead has been running so the "song position" is somewhere down the Arrangement Timeline to the right of zero, then if Playback starts again by the "Play" button up top, the Playhead just starts moving again from wherever it is. But if Playback starts again because a "session clip Play" button was pressed, then the Playhead jumps back to zero before Playback starts again, and the "whole song" is just that clip's loop length, the Playhead never goes past 4 bars or whatever, even though song length still keeps growing with every loop. I also think you can click the Play button on an Arrangement clip so Playback will jump to the beginning of that clip wherever it begins on the timeline. Correct? (and similarly, that clip length kind of constrains the song length while it is looping?)

Can "LED Ring display" code showing "progress toward such a known destination" properly account for all the various scenarios and act accordingly in all cases? I'm not sure...

Did this musing about how Playback gets triggered (Global Play versus Clip Play) foster any ideas for you?

markusschloesser commented 2 years ago

How do I access the min and max values of VPOT_DISPLAY_BOOST_CUT: (0x11, 0x1B), # 11 - 1B VPOT_DISPLAY_WRAP: (0x21, 0x2B), # 21 - 2B VPOT_DISPLAY_SPREAD: (0x31, 0x36), # 31 - 36 VPOT_DISPLAY_BOOLEAN: (0x20, 0x2B) ? Because I think I know how to proceed afterwards. Been wrecking my brain over that for the last 2 days

BilldarBagdar commented 2 years ago

:)

What does this code do?

    display_mode_cc_base = encoder_ring_led_mode_cc_values[self.__v_pot_display_mode][0]
    range_end = encoder_ring_led_mode_cc_values[self.__v_pot_display_mode][1] - display_mode_cc_base + 1

Edit: range_end is kind of poorly named, a better refactored named would be range_length.

markusschloesser commented 2 years ago

is that the range end or the range length? The whole "how to access a tuple in a dict" bs messes with my head πŸ€―πŸ™„πŸ˜‡ EDIT: Forget that, I think I solved that.

New question: How do I GET an int out of a property? calling the scaler function from C4Components with scaler = make_interpolater(0, Live.Song.Song.last_event_time, display_mode_cc_base, range_len) throws "unsupported operand type(s) for -: 'property' and 'int'" where Live.Song.... is the property I want to get

ANOTHER UPDATE: I was stupid and after googling "how to get int of property" for an hour, i realized I just needed to do scaler = make_interpolater(0, self.song().last_event_time, display_mode_cc_base, range_len) πŸ™„πŸ™„πŸ™„

now onwards to scaled_data = map(scaler, self.song().current_song_time) which throws 'float' object is not iterable

markusschloesser commented 2 years ago

final update for tonight:

                elif e.vpot_index() == encoder_12_index:

                    if self.__time_display.TimeDisplay__show_beat_time:
                        time_string = str(self.song().get_current_beats_song_time()) + ' '
                        upper_string2 += 'Bar:Bt:Sb:Tik '
                        lower_string2 += time_string
                    else:
                        time_string = str(self.song().get_current_smpte_song_time(self.__time_display.TimeDisplay__smpt_format)) + ' '
                        upper_string2 += 'Hrs:Mn:Sc:Fra '
                        lower_string2 += time_string

                    display_mode_cc_base = encoder_ring_led_mode_cc_values[VPOT_DISPLAY_WRAP][0]
                    range_len = encoder_ring_led_mode_cc_values[VPOT_DISPLAY_WRAP][1] - display_mode_cc_base + 1

                    scaler = make_interpolater(0, self.song().last_event_time, display_mode_cc_base, range_len)
                    data_list = range(int(self.song().current_song_time))
                    scaled_data = map(scaler, data_list)
                    self.__encoders.update_led_ring(scaled_data)

doesn't work, shows attribute error: 'list' object has no attribute 'update_led_ring' in self.__encoders.update_led_ring(scaled_data)

BilldarBagdar commented 2 years ago

not sure, but I think you don't need the map step. scaler is an interpolation method. You give it a value from one range it gives you back a value from the other range. "last event time" is the end of the big range, "current song time" is the index in that big range that you want to show as a scaled position in the smaller range. Right?

scaler = make_interpolater(0, self.song().last_event_time, display_mode_cc_base, range_len)
play_head = int(self.song().current_song_time)
led_ring_val = scaler(play_head)
self.__encoders.update_led_ring(led_ring_val)

Maybe?

One thing you should double check though is... the code I showed earlier was done that way to give the "tuple" argument Live requires when mapping the feedback. That make_interpolater function you've written wants beginning and end values of the ranges, not the beginning and length of the ranges. "last value" and "length" of ranges are only the same value when the range begins at zero and is continuous.

 display_mode_cc_first = encoder_ring_led_mode_cc_values[VPOT_DISPLAY_WRAP][0]
 display_mode_cc_last = encoder_ring_led_mode_cc_values[VPOT_DISPLAY_WRAP][1]

Edit: One other thing is just something to keep in mind because the computer only understands binary, but the "song length" range above is expressed in decimal (Int) notation and the "vpot display value range" is expressed in hexadecimal notation. Both decimal and hexadecimal values get converted to binary values before any computing happens, but humans can easily get confused. Especially if you are, for example, looking at the order-of-magnitude values right on the modulo 10, 100, 1000, 10,000, etc. Because "10" = 10 only if you are using decimal notation. "10"= 2 in binary, and "10"= 16 in hexadecimal.

markusschloesser commented 2 years ago

thanks!

re the raised issues:

  1. not sure, but I think you don't need the map step. scaler is an interpolation method. You give it a value from one range it gives you back a value from the other range. "last event time" is the end of the big range, "current song time" is the index in that big range that you want to show as a scaled position in the smaller range. Right?

correct

2.

scaler = make_interpolater(0, self.song().last_event_time, display_mode_cc_base, range_len)
play_head = int(self.song().current_song_time)
led_ring_val = scaler(play_head)
self.__encoders.update_led_ring(led_ring_val)

Maybe?

same attribute error as before: 'list' object has no attribute 'update_led_ring' in self.__encoders.update_led_ring(led_ring_val)

  1. One thing you should double check though is... the code I showed earlier was done that way to give the "tuple" argument Live requires when mapping the feedback. That make_interpolater function you've written wants beginning and end values of the ranges, not the beginning and length of the ranges. "last value" and "length" of ranges are only the same value when the range begins at zero and is continuous.

     display_mode_cc_first = encoder_ring_led_mode_cc_values[VPOT_DISPLAY_WRAP][0]
     display_mode_cc_last = encoder_ring_led_mode_cc_values[VPOT_DISPLAY_WRAP][1]

also doesn't change the error from appearing

  1. Edit: One other thing is just something to keep in mind because the computer only understands binary, but the "song length" range above is expressed in decimal (Int) notation and the "vpot display value range" is expressed in hexadecimal notation. Both decimal and hexadecimal values get converted to binary values before any computing happens, but humans can easily get confused. Especially if you are, for example, looking at the order-of-magnitude values right on the modulo 10, 100, 1000, 10,000, etc. Because "10" = 10 only if you are using decimal notation. "10"= 2 in binary, and "10"= 16 in hexadecimal.

Noted, and aware of it :-)

Will only have time later tonight to proceed

markusschloesser commented 2 years ago

where is the fucking list coming from?? πŸ€”πŸ€―:-) Also tried hardcoded values scaler = make_interpolater(0, self.song().last_event_time, 0x21, 0x2B) , doesn't makle a difference

markusschloesser commented 2 years ago

I got something working! πŸ’ͺ😁

                    scaler = make_interpolater(0, self.song().last_event_time, display_mode_cc_first, display_mode_cc_last)
                    play_head = int(self.song().current_song_time)
                    led_ring_val = int(scaler(play_head))
                    spp_vpot_index = 11
                    spp_vpot = self.__encoders[spp_vpot_index]
                    spp_vpot.update_led_ring(led_ring_val)

Unfortunately the scaling doesn't work properly (yet), but at last!

EDIT: strike that, it does work properly! When I create an event significantly later, it scales. So the last_event_time might be confusing at first in an empty track.

markusschloesser commented 2 years ago

pushed the code to GH (as you can see), now that needs to be tidied up and make using it a lot more convenient Oh and when you are in one of your "teachery/explanatory" moods at some time, I would love to know why it now works, but didn't before

BilldarBagdar commented 2 years ago

You got it...

where is the fucking list coming from??

A: self.__encoders

Edit: Would you like more of an explanation? I'm not sure how I can be any more clear than the code that now works.

What I can do is point out, if you don't mind, I suspect you took off on a tangent and had trouble getting back on the scent when you said,

same attribute error as before:

It was ALSO an attribute error, but not the SAME attribute error. My teacherous commentary would be something I read recently, "the compiler is trying its best to tell you exactly where it ran into the issue and what it was".

'list' object has no attribute 'update_led_ring' in self.__encoders.update_led_ring(led_ring_val)

Encoder objects have the attribute (i.e. method name or method) 'update_led_ring' right? How come the compiler thinks it is looking at a 'list' and not an 'Encoder'? Because self.__encoders IS a list of all 32 encoders, and that's your answer. You need the index of the Encoder you want from that list like your updated code.

markusschloesser commented 2 years ago

also just now did LoopStart and LoopLength (with VPOT_DISPLAY_SPREAD)

BilldarBagdar commented 2 years ago

nice. I got some fetching and pulling to do.

markusschloesser commented 2 years ago

to answer myself and for documentation:

  • Song/Beat Position Pointer: I need Song.start for Live_value_min, which doesn't exist in the Live Api. Can I just set this to 000:00:00:000 ?

Song.start is set to 0

  • for LoopLength I need to know the minimum Live loop_length, which I don't

minimum LoopLength set to 1

  • for LoopStart, it is basically the same as 1.

also set to 0