Page 2 of 2

Re: Transport CV out triggers

Posted: Sat Sep 03, 2022 5:24 pm
by Waverley Instruments
Aarnville wrote: Sat Sep 03, 2022 5:12 pmIt's this sort of stuff that CA could do with including in their dev kit documentation - a "best practice" list.
My audio development background is mostly iOS / Apple tech stack so actually having some developer documentation is a massive bonus. :lol:

Re: Transport CV out triggers

Posted: Sat Sep 03, 2022 7:46 pm
by ColinP
I usually handle trigger input detection inside ProcessSample() using something like this...

Code: Select all

   if( whateverIn.IsConnected() )
   {
      // detect rising edge...
      if( whateverIn.GetValue() >= TRIGGER_THRESHOLD )
      {
         // whatever input is high
         if( whateverInDetected == false )
         {
            // rising edge detected
            whateverInDetected = true;
            bingo();
         }
      }
      else
      {
         // trigger input is low
         whateverInDetected = false;   
      }
   }
   else
   {
      // disconnected...
      whateverInDetected = false;
   }
   
Initialte a trigger like so...

Code: Select all

   thingyOutCountdown = TRIGGER_LENGTH;
Handle trigger output in ProcessSample() like so...

Code: Select all

   if( thingyOutCountdown > 0 )
   {
      thingyOut.SetValue( TRIGGER_VOLTAGE );
      thingyOutCountdown--;
   }
   else
      thingyOut.SetValue( 0 );
      
Constants defined by...

Code: Select all

private static final double TRIGGER_THRESHOLD = 2.5;
private static final double TRIGGER_VOLTAGE = 5.0;
private static final int TRIGGER_LENGTH = 48;   // duration in samples of trigger outputs (1 ms)
Single sample duration triggers work fine most of the time but are fragile in complex patches because of propagation delays. Extending the trigger duration makes it less likely that logic signals that you would expect to match get lost because of one or two samples worth of delay somewhere along the road.

Re: Transport CV out triggers

Posted: Sat Sep 03, 2022 8:26 pm
by Aarnville
ColinP wrote: Sat Sep 03, 2022 7:46 pm I usually handle trigger input detection inside ProcessSample() using something like this...

Code: Select all

   if( whateverIn.IsConnected() )
   {
      // detect rising edge...
      if( whateverIn.GetValue() >= TRIGGER_THRESHOLD )
      {
         // whatever input is high
         if( whateverInDetected == false )
         {
            // rising edge detected
            whateverInDetected = true;
            bingo();
         }
      }
      else
      {
         // trigger input is low
         whateverInDetected = false;   
      }
   }
   else
   {
      // disconnected...
      whateverInDetected = false;
   }
   
Initialte a trigger like so...

Code: Select all

   thingyOutCountdown = TRIGGER_LENGTH;
Handle trigger output in ProcessSample() like so...

Code: Select all

   if( thingyOutCountdown > 0 )
   {
      thingyOut.SetValue( TRIGGER_VOLTAGE );
      thingyOutCountdown--;
   }
   else
      thingyOut.SetValue( 0 );
      
Constants defined by...

Code: Select all

private static final double TRIGGER_THRESHOLD = 2.5;
private static final double TRIGGER_VOLTAGE = 5.0;
private static final int TRIGGER_LENGTH = 48;   // duration in samples of trigger outputs (1 mS)
Single sample duration triggers work fine most of the time but are fragile in complex patches because of propagation delays. Extending the trigger duration makes it less likely that logic signals that you would expect to match get lost because of one or two samples worth of delay somewhere along the road.
Hi Colin,

My trigger detection is encapsulated in a class and my variable names are different but the code you have just put up is pretty much functionally identical to mine. Having a Trigger class makes ProcessSample a bit cleaner, especially when there are multiple trigger inputs.

The check reads something like:

Code: Select all

if (trigReset.Triggered(inputReset.GetValue()))  
{
  // triggered
and the rest of the detail is hidden in the class, which keeps track of the previous state.

Re: Transport CV out triggers

Posted: Sat Sep 03, 2022 8:57 pm
by ColinP
Hi Ian,

That's interesting. I have a "weight judgement" for class creation. As although one could well argue that a trigger class makes sense because it enables one to encapsulate the detection state, for me the scale doesn't quite tip.

So there are only 12 classes in my Granular Synth code while I suspect you might have used somewhere around 100.

Ultimately it's all a matter of personal taste and how one has learnt to manage complexity.

I note your code example uses a method called Triggered(). I find it really annoying that CA break the Java convention and make their method identifiers look like class names and I have to handle a disjointed thought and reach for the shift key every time I use a CA method name. What's your reasoning?

Re: Transport CV out triggers

Posted: Sun Sep 04, 2022 8:23 am
by Aarnville
ColinP wrote: Sat Sep 03, 2022 8:57 pm I note your code example uses a method called Triggered(). I find it really annoying that CA break the Java convention and make their method identifiers look like class names and I have to handle a disjointed thought and reach for the shift key every time I use a CA method name. What's your reasoning?
No reasoning, it's just that underneath it all I'm a C programmer and I am still absorbing the Java style so my earlier stuff in particular is sometimes inconsistent. You'll notice that I like my opening curly braces on their own line so the closing brace matches up with it. And as you have pointed out, the CA style is not very consistent with most of the Java world. It's not something I stress over though. As long as the code is readable and clear it's fine with me. It's not like I'll be contributing the code to a bigger project. (On a related subject: message to Cherry Audio devs. In the next version of the editor please can we have a "highlight matching braces" option. Ta.)

As for class counts I think I am almost always in single figures. My most recent release, the Big Purple Cord Sequencer, is easily the most complex module I've done so far and the class count might just tip over into double figures but not by much. That was the module where I learned about the 64k size limit on any Java class. My chord "wizard" class contains hundreds of chord progressions in a large constant array. During development it only had a handful of chord progressions for testing purposes so it was quite annoying to find the 64K limit right near the end of the project when I added in the rest of the data. A 64k limit in 2022!!! It's like going back to the days of memory models in 80x86 land. Grumble grumble grumble... :)

Re: Transport CV out triggers

Posted: Sun Sep 04, 2022 9:56 am
by ColinP
Aarnville wrote: Sun Sep 04, 2022 8:23 am No reasoning, it's just that underneath it all I'm a C programmer and I am still absorbing the Java style so my earlier stuff in particular is sometimes inconsistent.
Yeah, I'm fairly new to Java too and it took ages to reprogram my brain to use doSomething rather than do_something format. But the use of Capitalized method identifiers in CA's API drives me nuts.
Aarnville wrote: Sun Sep 04, 2022 8:23 am You'll notice that I like my opening curly braces on their own line so the closing brace matches up with it.
I totally agree about the brace style.

Code: Select all

if( a )
{
   b();
   c();
}
...is far easier to parse than...

Code: Select all

if (a) {
   b();
   c();
}
...especially in large scale use rather than the trivial example used above.

Whitespace and code aesthetics in general is important to me as I find it massively improves my efficiency.

My whitespace style is different to K&R as I think of things like if statements as special functions so don't use a space between if etc and the condition. But I do use a lot of extra space elsewhere as I find it makes it much easier to speed read. But I don't use spaces in a cast so something "dangerous" like (int) stands out prominently. The downside is that when I look at other people's work it looks like crazy code full of casts! :)
Aarnville wrote: Sun Sep 04, 2022 8:23 am (On a related subject: message to Cherry Audio devs. In the next version of the editor please can we have a "highlight matching braces" option. Ta.)
There is a CTRL } brace matching function in the VMD editor although I never use it.
Aarnville wrote: Sun Sep 04, 2022 8:23 am That was the module where I learned about the 64k size limit on any Java class. My chord "wizard" class contains hundreds of chord progressions in a large constant array. During development it only had a handful of chord progressions for testing purposes so it was quite annoying to find the 64K limit right near the end of the project when I added in the rest of the data. A 64k limit in 2022!!!
I've not come across that before. What exactly do you mean? A 64kB limit on constant literal text size or something else? Very curious.

Re: Transport CV out triggers

Posted: Sun Sep 04, 2022 10:45 am
by Aarnville
in starchy official docs wording:
4.10 Limitations of the Java Virtual Machine
The following limitations of the Java virtual machine are implicit in the classfile format:

* The per-class or per-interface constant pool is limited to 65535 entries by the 16-bit constant_pool_count field of the ClassFile structure (�4.1). This acts as an internal limit on the total complexity of a single class or interface.

* The amount of code per non-native, non-abstract method is limited to 65536 bytes by the sizes of the indices in the exception_table of the Code attribute (�4.7.3), in the LineNumberTable attribute (�4.7.8), and in the LocalVariableTable attribute (�4.7.9).
In practical terms it is 64k per "compiled" java file, whatever "compiled" means in Java-land. I reduced the entries in the array to see when it started working and none of the various sizes the VMD compiler reported matched a 64K size. The error message is a somewhat blunt "code too large" (Google it and revel in the mixture of annoying and helpful responses on StackOverflow). I guess it's a limit you only hit with very large constant arrays like mine. I could split it in two and put each in a different file/class but then it's not quite so convenient to use. I could also load it in at runtime from a file or, in VMD, from an "extra resource" but none of that is as clear as a single large array.

I reworked the array so it became a three dimensional byte array rather than a nice tidy array of classes within classes, which quartered the size and managed to squeeze just over 400 chord progressions into the 64K. The same byte array in C would occupy about 6K so there is still something I'm not understanding but 400 progressions was way more than my original target so that's where I stopped. Fortunately the original array was generated with a Python script so it was easy to change to the more compact format but it was grief I didn't need so near the end of a project. Like so many software projects, if I was to start over from scratch I'd do it a different way!

It's all learning...

Re: Transport CV out triggers

Posted: Sun Sep 04, 2022 12:00 pm
by ColinP
Thanks Ian, that's very interesting.

I think the limitations might be a little more complex and less limiting than you seem to be arguing.

The limitation appears to be 64k different constants not 64 kB total class size.

The 64 kB limit at the end of the documentation you quote is saying that the byte code size of an individual method can't exceed 64 kB not that the entire class has that limit.

From https://docs.oracle.com/javase/specs/jv ... #jvms-4.10
4.11. Limitations of the Java Virtual Machine
The following limitations of the Java Virtual Machine are implicit in the class file format:

The per-class or per-interface constant pool is limited to 65535 entries by the 16-bit constant_pool_count field of the ClassFile structure (§4.1). This acts as an internal limit on the total complexity of a single class or interface.

The number of fields that may be declared by a class or interface is limited to 65535 by the size of the fields_count item of the ClassFile structure (§4.1).

Note that the value of the fields_count item of the ClassFile structure does not include fields that are inherited from superclasses or superinterfaces.

The number of methods that may be declared by a class or interface is limited to 65535 by the size of the methods_count item of the ClassFile structure (§4.1).

Note that the value of the methods_count item of the ClassFile structure does not include methods that are inherited from superclasses or superinterfaces.

The number of direct superinterfaces of a class or interface is limited to 65535 by the size of the interfaces_count item of the ClassFile structure (§4.1).

The greatest number of local variables in the local variables array of a frame created upon invocation of a method (§2.6) is limited to 65535 by the size of the max_locals item of the Code attribute (§4.7.3) giving the code of the method, and by the 16-bit local variable indexing of the Java Virtual Machine instruction set.

Note that values of type long and double are each considered to reserve two local variables and contribute two units toward the max_locals value, so use of local variables of those types further reduces this limit.

The size of an operand stack in a frame (§2.6) is limited to 65535 values by the max_stack field of the Code attribute (§4.7.3).

Note that values of type long and double are each considered to contribute two units toward the max_stack value, so use of values of these types on the operand stack further reduces this limit.

The number of method parameters is limited to 255 by the definition of a method descriptor (§4.3.3), where the limit includes one unit for this in the case of instance or interface method invocations.

Note that a method descriptor is defined in terms of a notion of method parameter length in which a parameter of type long or double contributes two units to the length, so parameters of these types further reduce the limit.

The length of field and method names, field and method descriptors, and other constant string values (including those referenced by ConstantValue (§4.7.2) attributes) is limited to 65535 characters by the 16-bit unsigned length item of the CONSTANT_Utf8_info structure (§4.4.7).

Note that the limit is on the number of bytes in the encoding and not on the number of encoded characters. UTF-8 encodes some characters using two or three bytes. Thus, strings incorporating multibyte characters are further constrained.

The number of dimensions in an array is limited to 255 by the size of the dimensions opcode of the multianewarray instruction and by the constraints imposed on the multianewarray, anewarray, and newarray instructions (§4.9.1, §4.9.2).
The 16 bit constant_pool_count field seems to refer not to the number of bytes but the number of structures...
constant_pool_count
The value of the constant_pool_count item is equal to the number of entries in the constant_pool table plus one. A constant_pool index is considered valid if it is greater than zero and less than constant_pool_count, with the exception for constants of type long and double noted in §4.4.5.

constant_pool[]
The constant_pool is a table of structures (§4.4) representing various string constants, class and interface names, field names, and other constants that are referred to within the ClassFile structure and its substructures. The format of each constant_pool table entry is indicated by its first "tag" byte.
I have absolutely no doubt that you've managed to hit some limit but from my quick reading of the Oracle documentation I'm not sure what it is.

Could you share some template code that would (when scaled up) demonstrate the problem?

Re: Transport CV out triggers

Posted: Sun Sep 04, 2022 1:09 pm
by Aarnville
It's not something I'm going to spend much more time on but the array doesn't have to be anything special. It just has to be big.

Paste this into any of your modules and increase the size by adding more rows of numbers until compilation fails - you probably need more than 100 rows with 100 ints per row to hit the failure. It doesn't matter whether the array is static or final or neither.

Code: Select all

  private static final int[] bigArray = new int[] 
  {  
    0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,
    0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,
  };

@Rob, apologies for the thread derail but at least you got some sensible answers before we meandered off into distraction! :)

Re: Transport CV out triggers

Posted: Sun Sep 04, 2022 3:57 pm
by ColinP
The thread derail is my fault so mea culpa to everyone but I figure we are all adults and if there's knowledge to be gained then WTF.

Anyway thanks Ian. The problem was easy to reproduce. But I don't think it's got anything to do with class size limits. Even if you drop the redundant new int[] the static initialization will be executed by a synthetic method and I'm pretty sure it's the initialization method containing more that 64 kB of code that is causing the "code too large" error.

Fairly easy to fix, but you've found a solution anyway.

If you look at the automated output from VMD when there's a large number of GUI components it produces code like...

Code: Select all

   InitializeControls();
   InitializeControls2();
   InitializeControls3();
...where it breaks down the initialization into chunks to avoid any risk of having methods larger than 64 kB in size.

I guess it would be pretty easy to fix in the Java compiler by improving static initialization code generation but a) nearly 30 years ago nobody would have thought that a method might be larger than 64 kB and b) such large data sets are very rarely handled as literals. Still it's a Java weakness that I wasn't previously aware of so I'm glad you raised the issue.