Kara-Moon Forum

Developers & Technology => Musical MIDI Accompaniment (MMA) => Topic started by: sciurius on May 18, 2020, 07:41:49 PM



Title: Subroutines
Post by: sciurius on May 18, 2020, 07:41:49 PM
Reflecting on the 'Truncate and Subroutines'...

Code:
Set Tempo 120

Begin Plectrum
    Voice       NylonGuitar
End

/**************** Plectrum patterns ****************/

Begin Plectrum Define
      G4E       1.0   0   6:90 5-4:0;   \
              1.5   0   3-1:70;       \
        2.0   0   4:70;         \
        2.5   0   3-1:70;       \
        3.0   0   6:80;         \
        3.5   0   3-1:70;       \
        4.0   0   4:70;         \
        4.5   0   3-1:70
 
      G4A 1.0   0   5:90 6:0 4:0; \
      1.5   0   3-1:70;       \
2.0   0   4:70;         \
2.5   0   3-1:70;       \
3.0   0   5:80;         \
3.5   0   3-1:70;       \
4.0   0   4:70;         \
4.5   0   3-1:70
 
      G4D 1.0   0   4:90 6:0 5:0; \
      1.5   0   3-1:70;       \
2.0   0   5:70;         \
2.5   0   3-1:70;       \
3.0   0   4:80;         \
3.5   0   3-1:70;       \
4.0   0   5:70;         \
4.5   0   3-1:70
End

DefCall G4E Chords
Plectrum Sequence G4E
  1    $Chords
EndDefCall
Set G4E Call G4E

DefCall G4A Chords
Plectrum Sequence G4A
  1    $Chords
EndDefCall
Set G4A Call G4A

DefCall G4D Chords
Plectrum Sequence G4D
  1    $Chords
EndDefCall
Set G4D Call G4D

/**************** Song parts ****************/

$G4A   Am * 2
$G4D   Dm * 2
$G4E   E  * 2
$G4A   Am * 2

Finally a way to set a pattern and chord in one line.


Title: Re: Subroutines
Post by: bvdp on May 19, 2020, 02:12:06 AM
I really like subroutines ... and we should try to used them more!

The only problem with them is that you can't have a variable number or arguments ... so in your case, you'd need a different sub for "Am", "Am / Gm /", etc. You could use macros to hold the chords and pass that to the subroutine, but that might be silly.

I'm going to have to play with this and see if I can get it work. Maybe it already does ... way too much code here for me to remember what/why I've written.


Title: Re: Subroutines
Post by: bvdp on May 19, 2020, 03:29:53 AM
What you think about introducing a grouping option? I'm thinking the ()s not used at this point ... so, we could say that stuff inside them are to be assigned to a subroutine arg. So, my earlier example:

Code:
DefCall To54 chord
  Truncate 1
  $Chord
  $Chord
EndDefCall

would work fine using:

Code:
Call To54 (Am / Gm)   /// 3 chords to CHORD
Call To54 F                ///  1 chord to CHORD


Just a matter of taking the stuff in () and assigning it to "chord" as one chunk. Just now (and it's getting late) I don't see why this wouldn't work.

Comments?


Title: Re: Subroutines
Post by: sciurius on May 19, 2020, 08:48:01 AM
Quote
I really like subroutines ... and we should try to used them more!

Most certainly. We're programmers, after all :) .

Quote
The only problem with them is that you can't have a variable number or arguments ...

I think we're hitting a very low-level design issue of MMA: the parser.

Currently, source lines are split into whitespace separated items (let's call them 'words') immedeately after reading. This means you cannot write things like (in a sequence)
  1.5   0   3-1:$LOUD
since the macro needs to be a word. You cannot write (in the .mmarc)
  SetLibPath My Documents/mma
unless you want your stuff in the folders "My" and "Documents/mma".

Likewise, (Am G C) are three words, "(Am", "G", and "C)". You can artificially apply some heuristics on leading and trailing parens but it is clumsy and error prone. The same goes for good old strings, e.g. print "A             B" will print two words, ""A" and "B"" instead of the (more logical) string "A             B".

A token based parser would be much better. I did some experiments but unfortunately (or maybe fortunately) I lost them... Using a tokenizing parser would have a drastic impact and probably break a lot of songs...




Title: Re: Subroutines
Post by: bvdp on May 19, 2020, 04:17:14 PM
Yup.

If I were to start all over again I'd make different decisions. Unfortunately, MMA was really designed to be a very simple, super metronome and then morphed into the monster we now have. Still, it works pretty well. After all, how often do you need to print strings with quotation marks when creating a midi file? And that was then and we're now here in the present :)

But, adding () to the subroutine code should be painless and useful. As always, give me a day or so.


Title: Re: Subroutines
Post by: sciurius on May 20, 2020, 06:26:15 AM
I don't see the problem with multiple arguments. When called with several arguments you can slice:

 DefCall MySub Arg
    print Arg = $Arg         // all args
    print Arg 1 = $ARG[0]    // first word of arg
    print Arg 2 = $ARG[1]    // second word of arg
    ...


Using multiple variables would be nice, though:

 DefCall MySub Arg1 Arg2 Arg3
     print Arg 1 = $Arg1
     print Arg 2 = $Arg2
     ...


Another option would be a special function, e.g.

  $_SUBARG(0)            // number of words in arg
  $_SUBARG(1)            // 1st word of arg
  $_SUBARG(2)            // 2nd word
  ...


This would allow looping over the arguments.


Title: Re: Subroutines
Post by: bvdp on May 20, 2020, 04:14:14 PM
I don't see the problem with multiple arguments. When called with several arguments you can slice:

 DefCall MySub Arg
    print Arg = $Arg         // all args
    print Arg 1 = $ARG[0]    // first word of arg
    print Arg 2 = $ARG[1]    // second word of arg
    ...



Only problem here is that you need to know in advance how many args there are :) Looking (just looking!) at () is on the top of my pile of things to do.


Title: Re: Subroutines
Post by: bvdp on May 20, 2020, 04:33:42 PM
Bugger, here I coded the [] code and then I forget about it :) So, for my original code the following works:

Code:

DefCall To54 chord
  print $Chord[:]
  Truncate 1
  $Chord[0]
  $Chord
EndDefCall

Call To54  Bm Cm F G
Call To54 G
Call To54 Dm
Call To54 C

Neato :) Maybe I don't need to look at () after all (which would make my life much simpler!).


Title: Re: Subroutines
Post by: sciurius on May 20, 2020, 05:33:29 PM
Given that slicing tries to follow python syntax it should be noted that $var[] should be invalid.

With the attached very simple patch to MMA/macro.py $var[] now returns the number of words in $var. Do we need more?



Title: Re: Subroutines
Post by: bvdp on May 21, 2020, 01:43:42 AM
Actually, this is quite brilliant. We can now do something like:

Code:

DefCall To54 chord
  set numargs $( $Chord[] )
  Truncate 1
  if gt $numargs  1
       $Chord[0]
  else
      $chord
  endif
  $Chord
EndDefCall

Call To54  Bm Cm F G
Call To54 G
Call To54 Dm
Call To54 C


Of course, it's no better than my earlier example and the if/then is completely redundant ... but taking actions inside a subroutine based on the number of arguments could be a lot of fun. I think there could be lots of uses for this in the future! I will post a new developer release in a few days ... hopefully, a new mainstream release will be "real soon now" as well.

Thanks so much!


Title: Re: Subroutines
Post by: sciurius on May 21, 2020, 06:23:34 AM
  set numargs $( $Chord[] )

Why not

  set numargs $Chord[]



Title: Re: Subroutines
Post by: bvdp on May 21, 2020, 04:19:40 PM
Why not

  set numargs $Chord[]


Aw, that'd be way to simple :)

Of course, the other way around this is to add len() to the permitted ops in safe_eval ... but, I think leaving it as is will be just fine.


Title: Re: Subroutines
Post by: bvdp on May 21, 2020, 05:38:00 PM
What is really nutty is that one really doesn't need to know the number of args for my little subrouting at all. Using:

 
Code:
DefCall To54 chord
  Truncate 1   
  $Chord[:1]
  $Chord
EndDefCall

Call To54  Bm Cm F G
Call To54 G
Call To54 / / B F

Works just fine with any number of chords passed :)


Title: Re: Subroutines
Post by: bvdp on June 19, 2020, 02:22:42 AM
I was adding this code to the "egs" directory for MMA and caught some faults :) This bit will work much better:

Code:
DefCall To54 chord
  Truncate 1
  $Chord[:1]
  Set Chord $Chord[1:]
  Set Length $Chord[]
  If Eq $Length 0
     Set Chord /
  Endif
  $Chord
EndDefCall


The only issue with this bit of code is that if you decide to use the "@" modifier to place the offsets of chords it will not work.

I really hope this bit of code and discussion helps someone in the future.