< Back to IRCAM Forum

Antescofo Language Question(s): Tab Comprehension and Predicates

I’m not sure if I fully grasp some language features of Antescofo, maybe I’m overlooking something. I admit that the functional paradigm is not always in concordance with the way my brain tends to approach a problem at first. The problem I want to solve is the following:

  • I get a flat list where the elements with even index represent consecutive time points and the odd ones loudness values.
  • I want to find the maximum loudness value within a given time region.

After abandoning the intuitive loop approach my second idea was to use tab comprehension and predicates but I did not find constructs in the documentation which I think I’d need, most of all to filter a tab (create a sub-tab) by predicate. I finally found a solution by somewhat misusing the @find function, somewhat because I want to find something but nevertheless misusing because it is not @find which returns the result (the predicate function always returns false) but a kind of side-effect. That side-effect is achieved by currying the predicate with a one-element tab as value holder and the arguments for the region.

@fun_def pred_maxinregion($valmax, $tmin, $tmax, $i, $v) { 
	if ($v[0] >= $tmin && $v[0] < $tmax) {
		if ($v[1] > $valmax[0]) {
			$valmax[0] := $v[1]
		}
	}
	return false
}
group TestFindMaxInRegion {
    @local $valueholder, $maxfound
    $valueholder := tab [-100] // initialize with a low value
    $tab1 := tab [0, -20, 1, 0, 2, -20, 3, -5, 4, -30, 5, 0] // example input
    $tab2 := @reshape($tab1, [@size($tab1) / 2, 2]) // two-dimensional array
    @find($tab2, @pred_maxinregion($valueholder, 2, 5)) // iterate it
    $maxfound := $valueholder[0] // save the result
}

And yes, this example finds [-5]. But is there a better way to obtain the result?

Hello Kyl.

Your approach is good. You use a trick : putting the current max value in an array so you can alter it in an imperative manner across the iteration of @find. There is no problem with that and it is efficient. With a loop it may have been written:

  @fun_def select_max($tmin, $tmax, $tab)
  {
    @local
       $maxval := -100,
       $t := @reshape($tab1, [@size($tab1) / 2, 2])
    
    forall $e in $t
      {
         if ($tmin <= $e[0] && $e[0] < $tmax && $maxval < $e[1])
           { $maxval := $e[1] }
      }

    return $maxval
  }

and the call is:

  @select_max(2, 5, $tab1)

Perhaps a functional programmer will have used the @reduce function. The tricky part is to initialize the iteration: the first time the reduction function is used, the first two elements of the tab are given. Then, the reduction function is feed with the result of the previous reductions and a tab element. We can distinguish the two cases because tab elements are pairs and the reduction value is a not. So:

  @fun_def maxelem($tmin, $tmax, $maxval, $e)
  {
    if ($maxval.is_tab())
      {
         if ($tmin <= $e[0] && $e[0] < $tmax)
           { return $maxval[1] }
         else
           { return -100 }
      }
    else
      {
         if ($tmin <= $e[0] && $e[0] < $tmax && $maxval < $e[1])
           { return $e[1] }
         else
           { return $maxval }
      }
  }

and the call:

   @reduce(@maxelem(2, 5), $tab2)

The necessity do distinguish the first iteration and the rest of them is not particularly elegant. Sometimes an alternative version of the @reduce function is proposed with an additional argument to provide a default value of the reduction. We consider to add it in a next release.

You mention also tab comprehension. Here is an alternative way to compute the max using tab comprehension to filter the time domain

[ $e[1] | $e in @reshape($tab1, [@size($tab1) / 2, 2]), 2 <= $e[0] && $e[0] < 5].max() 

which is short but perhaps a little bit cryptic: the comprehension iterates on @reshape($tab1, [@size($tab1) / 2, 2]) and select the element $e such that 2 <= $e[0] && $e[0] < 5. Only $e[1] is collected to build the result on which we apply @max (@max on tab selects the maximal element).

Hello JLG,

thank you for the hints to a more functional approach!

The use of @reduce function indeed looks sophisticated. But I guess it’s worth to become more acquainted with the concept.

The tab comprehension way looks elegant and I almost was on the way to it but then I stumbled over the question how to parameterize the indices in the predicate. How would you do that, i.e. pass variables for tmin and tmax in place of the hard indices?

You can put the tab comprehension in a function whose arguments contains tmin and tmax:

  @fun_def @select_max($tmin, $tmax, $tab)
  {
      @local $t := @reshape($tab, [@size($tab) / 2, 2])
      return [ $e[1] | $e in $t,  $tmin <= $e[0] && $e[0] < $tmax].max()
  }

and the call

  @select_max(2, 5, $tab1)

Indeed, the body of the tab comprehension, the iterator, etc., are arbitrary expressions.

This approach build an auxilliary tab (the comprehension), so it is a little bit less efficient than the iterative version. But you will not see a difference for tab for less than 10000 elements…

Great! I like that variant the most. Thank you for the lesson!