Thoughts on yield

I’ve begun working on a music-related ruby project, and recently I’ve
been pondering the idea of a music composition environment somewhat
similar to cm, clm and friends for lisp, but in ruby instead.

Today I was thinking about how ruby blocks make constructing
domain-specific languages a snap. I’ve been reading Lisp as a Second
Language at http://www.ircam.fr/equipes/repmus/LispSecondLanguage/, a
Lisp tutorial for musicians, and was considering using a list-based
structure for storing music. I sorta envisioned the language as
looking something like:

clef do
measure(“4/4”) do
c d e f g a b c
chord do
c e g
end
end
end

In this example, a-g are methods returning note objects.

What I’d like is to have that return a list like so:

[“c”, “d”, “e”, “f”, “g”, “a”, “b”, “c”, [“c”, “e”, “g”]]

But, as it stands, each expression is evaluated and discarded. I’d
like to avoid having to include actual lists in the blocks; while
arbitrary ruby expressions would be possible, I’m aiming to hide much
of that from the casual user.

So, my question. Is there any way to achieve something like that using
blocks? Is there any way to have yield do something other than run the
given code?

Assuming not, I was wondering how practical/horrible it would be to
have yield accept an optional block which, when given one parameter,
sets that parameter to the value of every top-level expression run
within the block? So, for instance, if I wanted to evaluate all of the
above expressions and concatenate their results into a list, I could
do:

score = []
yield { |x| score += [x] }

Of course, I realize that probably breaks the principle of least
surprise in a few instances, as many would expect the contents of a
block to be executed and only the result of all code returned, but in
my rather humble and untrained opinion, this would be useful for
implementing domain-specific languages.

Or is there a better way to accomplish what I’m trying?

In article 87brt5wz83.fsf@ethereal.dyndns.org,

I’ve begun working on a music-related ruby project, and recently I’ve
been pondering the idea of a music composition environment somewhat
similar to cm, clm and friends for lisp, but in ruby instead.

Today I was thinking about how ruby blocks make constructing
domain-specific languages a snap. I’ve been reading Lisp as a Second
Language at http://www.ircam.fr/equipes/repmus/LispSecondLanguage/, a
Lisp tutorial for musicians, and was considering using a list-based
structure for storing music. I sorta envisioned the language as
looking something like:

clef do
measure(“4/4”) do
c d e f g a b c
chord do
c e g
end
end
end

In this example, a-g are methods returning note objects.

What I’d like is to have that return a list like so:

[“c”, “d”, “e”, “f”, “g”, “a”, “b”, “c”, [“c”, “e”, “g”]]

I played with this problem for a few minutes and this is what I came up
with:

module Music

def a(list=@notes)
list << “a”
end

def b(list=@notes)
list << “b”
end

def c(list=@notes)
list << “c”
end

def d(list=@notes)
list << “d”
end

def e(list=@notes)
list << “e”
end

def f(list=@notes)
list << “f”
end

def g(list=@notes)
list << “g”
end

def clef
puts “…In Clef…”
yield
return @notes
end

def measure(time)
puts “…In Measure…”
yield
end

def chord
puts “…In Chord…”
yield @chord_notes
@notes << @chord_notes
end

def notes
@notes
end
end

class Song
include Music
def initialize
@notes =
@chord_notes=
end
end

class MySong < Song
include Music
def initialize
super
clef do
measure(“4/4”) do
c; d; e; f; g; a; b; c;
chord do |chrd|
c(chrd); d(chrd); g(chrd);
end
end
end
end
end

mysong = MySong.new
p mysong.notes

Of course, the ugly bit is the part in the chord block: you have to
pass in ‘chrd’ to all of the a-g methods to get those notes to be added to
the correct list (@chord_list) which then gets appended to the @notes
list. So it’s still not quite what you want (I understand that you want
people to see very little of the ‘wizard behind the curtain’ [Ruby] and
having to pass ‘chrd’ sort of negates that). Hopefully, someone else
will offer a suggestion for getting rid of that requirement.

…you may not like the Song class bit I added either…

Phil

···

Nolan J. Darilek nolan_d@bigfoot.com wrote:

At the basic level do you want

chord { c e g }

to return [“c”, “e”, “g”]?

It seems that you want to generate a list
using a lisp syntax. And to do that inside
a block, you probably need a hook to catch
the return value of each statment.
I’m not sure that would be advisable.

Have you considered parsing a string and using eval?
For example, do:

chord %{ c e g }

Of course, some smarter people than me may provide
the answer you are looking for.

···

On Sunday, 28 September 2003 at 9:30:15 +0900, Nolan J. Darilek wrote:

clef do
measure(“4/4”) do
c d e f g a b c
chord do
c e g
end
end
end

In this example, a-g are methods returning note objects.

What I’d like is to have that return a list like so:

[“c”, “d”, “e”, “f”, “g”, “a”, “b”, “c”, [“c”, “e”, “g”]]

But, as it stands, each expression is evaluated and discarded. I’d
like to avoid having to include actual lists in the blocks; while
arbitrary ruby expressions would be possible, I’m aiming to hide much
of that from the casual user.


Jim Freeze

A celebrity is a person who is known for his well-knownness.

In article 87brt5wz83.fsf@ethereal.dyndns.org,

I’ve begun working on a music-related ruby project, and recently I’ve
been pondering the idea of a music composition environment somewhat
similar to cm, clm and friends for lisp, but in ruby instead.

Today I was thinking about how ruby blocks make constructing
domain-specific languages a snap. I’ve been reading Lisp as a Second
Language at http://www.ircam.fr/equipes/repmus/LispSecondLanguage/, a
Lisp tutorial for musicians, and was considering using a list-based
structure for storing music. I sorta envisioned the language as
looking something like:

clef do
measure(“4/4”) do
c d e f g a b c
chord do
c e g
end
end
end

In this example, a-g are methods returning note objects.

What I’d like is to have that return a list like so:

[“c”, “d”, “e”, “f”, “g”, “a”, “b”, “c”, [“c”, “e”, “g”]]

I did a little more work on this; now I don’t have to pass in a list to
the notes from within the chord block:

module Music

def a(list=@tmp_notes)
list << “a”
end

def b(list=@tmp_notes)
list << “b”
end

def c(list=@tmp_notes)
list << “c”
end

def d(list=@tmp_notes)
list << “d”
end

def e(list=@tmp_notes)
list << “e”
end

def f(list=@tmp_notes)
list << “f”
end

def g(list=@tmp_notes)
list << “g”
end

def clef
puts “…In Clef…”
yield
@notes = @tmp_notes
@notes << @chord_notes unless @chord_notes.empty?
return @notes
end

def measure(time)
puts “…In Measure…”
yield
end

def chord
#make a local copy of @tmp_notes
tmpnotes = @tmp_notes.dup
@tmp_notes.clear
puts “…In Chord…”
yield @chord_notes
@chord_notes = @tmp_notes
#restore @tmp_notes
@tmp_notes = tmpnotes
end

def notes
@notes
end
end

class Song
include Music
def initialize
@notes =
@chord_notes=
@tmp_notes =
end
end

class MySong < Song
include Music
def initialize
super
clef do
measure(“4/4”) do
c; d; e; f; g; a; b; c;
chord do
c; d; g;
end
end
end
end
end

mysong = MySong.new
p mysong.notes

So that gets rid of some of the ugliness that the user of your music
language has to know (you want them to not have to know Ruby at all), but
the super is still there.

Perhaps you could do something like this instead of subclassing Song, you
could do (untested):

Defined by you:
class Song
include Music
def initialize
@notes =
@chord_notes=
@tmp_notes =
yield
end
end

Defined by user in a different file:

my_song= Song.new {
clef do
measure(“4/4”) do
c; d; e; f; g; a; b; c;
chord do
c; d; g;
end
end
end
}

The advantage being that you can eliminate the need for the user to have
to define a class and call super. You could even add more methods to
either Song or Music so that you could do:

my_song.play

…but I’m not sure that will work; the block being passed to Song.new has
to know about all of the methods we’re using in it (a-f refer to
@temp_list which doesn’t have a context in this block). There’s probably
some way of doing this…

Phil

···

Nolan J. Darilek nolan_d@bigfoot.com wrote:

Here’s take 3. This one seems to work:

module Music
def initialize
@notes = []
@chord_notes=[]
@tmp_notes = []
end

def a(list=@tmp_notes)
list << "a"
end

def b(list=@tmp_notes)
list << "b"
end

def c(list=@tmp_notes)
list << "c"
end

def d(list=@tmp_notes)
list << "d"
end

def e(list=@tmp_notes)
list << "e"
end

def f(list=@tmp_notes)
list << "f"
end

def g(list=@tmp_notes)
list << "g"
end

def clef
puts "…In Clef…"
yield
@notes = @tmp_notes
@notes << @chord_notes unless @chord_notes.empty?
return @notes
end

def measure(time)
puts "…In Measure…"
yield
end

def chord
tmpnotes = @tmp_notes.dup
@tmp_notes.clear
puts "…In Chord…"
yield @chord_notes
@chord_notes = @tmp_notes
@tmp_notes = tmpnotes
end

def notes
@notes
end
end

class Song
include Music

def define_song(&b)
instance_eval &b
end
end

mysong= Song.new
mysong.define_song {
clef do
measure(“4/4”) do
c; d; e; f; g; a; b; c;
chord do
c; d; g;
end
end
end
}
p mysong.notes

This way the user doesn’t even have to define a class, call super and all
those Ruby details.

Thanks to Joel for the instance_eval suggestion.

Phil

nolan_d@bigfoot.com (Nolan J. Darilek) wrote:

I sorta envisioned the language as looking something like:

clef do
measure(“4/4”) do
c d e f g a b c
chord do
c e g
end
end
end

In this example, a-g are methods returning note objects.

What I’d like is to have that return a list like so:

[“c”, “d”, “e”, “f”, “g”, “a”, “b”, “c”, [“c”, “e”, “g”]]

Possible. But not for long.

#!/usr/bin/ruby -w

def clef
yield
end

def measure(m)
@lines =
yield
@lines.inject {|a,b| a.push(*b)}
end

def chord
@lines[-1] = [yield]
end

(‘a’…‘g’).each {|x| eval <<“”}
def #{x}(*a)
@lines << if a.empty?
@lines[-1].unshift(‘#{x}’)
end

notes = clef do
measure(“4/4”) do
c d e f g a b c
chord do
c e g
end
end
end

p notes

END
…/gem:25: warning: parenthesize argument(s) for future version
…/gem:25: warning: parenthesize argument(s) for future version
…/gem:25: warning: parenthesize argument(s) for future version
…/gem:25: warning: parenthesize argument(s) for future version
…/gem:25: warning: parenthesize argument(s) for future version
…/gem:25: warning: parenthesize argument(s) for future version
…/gem:27: warning: parenthesize argument(s) for future version
[“c”, “d”, “e”, “f”, “g”, “a”, “b”, “c”, [“c”, “e”, “g”]]

Phil Tomson wrote:

Of course, the ugly bit is the part in the chord block: you have to
pass in ‘chrd’ to all of the a-g methods to get those notes to be added to
the correct list (@chord_list) which then gets appended to the @notes
list. So it’s still not quite what you want (I understand that you want
people to see very little of the ‘wizard behind the curtain’ [Ruby] and
having to pass ‘chrd’ sort of negates that). Hopefully, someone else
will offer a suggestion for getting rid of that requirement.

I suggest our old friend instance_eval.

···

diff -u music-orig.rb music.rb
— music-orig.rb 2003-09-27 22:04:11.000000000 -0700
+++ music.rb 2003-09-27 22:03:43.000000000 -0700
@@ -41,8 +41,9 @@

def chord
  puts "...In Chord..."
  • yield @chord_notes
  • @notes << @chord_notes
  • @chord_notes.instance_eval &Proc.new
  • @notes << @chord_notes.notes.dup
  • @chord_notes.clear
    end
def notes

@@ -50,23 +51,32 @@
end
end

+class Chord

  • include Music
  • def initialize
  • @notes =
  • end
  • def clear
  • @notes.clear
  • end
    +end
  • class Song
    include Music
    def initialize
    @notes =
  • @chord_notes=
  • @chord_notes=Chord.new
    end
    end

class MySong < Song

  • include Music
    def initialize
    super
    clef do
    measure(“4/4”) do
    c; d; e; f; g; a; b; c;
  •    chord do |chrd|
    
  •      c(chrd); d(chrd); g(chrd);
    
  •    chord do
    
  •      c; d; g;
        end
      end
    end
    

What about an algebraic approach?

Instead of

 clef do
   measure("4/4") do
     c; d; e; f; g; a; b; c;
     chord do
       c; d; g;
     end
   end
 end

maybe

 clef do
   measure("4/4") do
    c + d + e + f + g + a + b + c + c*d*g
   end
 end

You could denote quarter notes with b/4 etc.

Like Jim, my feeling is that some parsing will be
required. There’s no C# method in Ruby.

Allows for sharps, flats and naturals.

My effort was also helped by Joel’s input.

Unique Note objects are created for no reason other
than folks (and my) amusement.

···

“Jim Freeze” jim@freeze.org wrote:

Nolan J. Darilek wrote:

Have you considered parsing a string …
For example, do:

chord %{ c e g }

#====================

module Music

class Clef
def initialize
@notes =
end

def time_sig(beats, beatval)
  @beats   = beats
  @beatval = beatval
end

def chord(c)
  @notes << c.split.map {|ce| Note[ce] }
end

def notes(n=nil)
  if n
    @notes << n.split.map {|ne| Note[ne] }
  else
    @notes
  end
end

def inspect
  @n
end

end

class Note
private_class_method :new
@@note_h = Hash

def Note.[](n)
  puts "Bum Note - #{n}" if n !~ /[a-g][#_@]?/i
  nn = @@note_h[n] || @@note_h[n] = new(n)
end

def initialize(n)
  @n = n
end

def inspect
  @n
end

end

def clef(&b)
clf = Clef.new
clf.instance_eval(&b)
r = clf.notes
p r
r
end
end

#====================

include Music

clef do
time_sig(4,4)
notes %{d e f# g a b c# d}
chord %{c d g}
end

clef do
time_sig(3,4)
notes %{a_ b@ c# d}
chord %{d k}
end

#====================

#-> [[d, e, f#, g, a, b, c#, d], [c, d, g]]
#-> Bum Note - k
#-> [[a_, b@, c#, d], [d, k]]

daz

Sabby and Tabby wrote:

(‘a’…‘g’).each {|x| eval <<“”}
def #{x}(*a)
@lines << if a.empty?
@lines[-1].unshift(‘#{x}’)
end

I didn’t realize that the first line didn’t count for here documents, so you
could close blocks and such. I thought you would have needed:

(‘a’…‘g’).each {|x| eval <<“”
def #{x}(*a)
@lines << if a.empty?
@lines[-1].unshift(‘#{x}’)
end

}

You learn something new every day, I guess.

  • Dan

Beautiful. Pity it’s going away.

martin

···

Sabby and Tabby sabbyxtabby@yahoo.com wrote:

Possible. But not for long.

#!/usr/bin/ruby -w

def clef
yield
end

def measure(m)
@lines =
yield
@lines.inject {|a,b| a.push(*b)}
end

def chord
@lines[-1] = [yield]
end

(‘a’…‘g’).each {|x| eval <<“”}
def #{x}(*a)
@lines << if a.empty?
@lines[-1].unshift(‘#{x}’)
end

notes = clef do
measure(“4/4”) do
c d e f g a b c
chord do
c e g
end
end
end

p notes

I wrote:

def chord(c)
  @notes << c.split.map {|ce| Note[ce] }    # OK
end

def notes(n=nil)
  if n
    @notes << n.split.map {|ne| Note[ne] }  #  Oops !!

^^

      @notes += n.split.map {|ne| Note[ne] }
  else
    @notes
  end
end

#====================

Oops, I pushed the note array instead of joining.
Don’t want people to think I don’t care :wink:

#-> [d, e, f#, g, a, b, c#, d, [c, d, g]]

daz

There are however unary +, -, and ~ operators, which might translate
appropriately.

However there are a lot of intricacies to music in general, and I’m
not sure any of the given methods quite handle them all. Some
everyday things to think about:

  • Tuples
  • Multiple voices (not just simple chords)
  • Note length tweaks (for slurs and the like)
  • Rests (big one)
  • Tempo changes
  • Dynamics

Arrays might help some things, as well as combining voices on the fly,
but it may not be as straightforward as desired.

Hmm, a little method_missing magic and we could have a vaguely
track-like view perhaps:

part {
  bar 16      # Set each _ to be a 16th, so ____ is 1/4

  v1  a____,     r_, ...
  v2  r_, b__, +c__, ...       # r is for rest

  v1  ...
  v2  ...
}

In this case, you could chop off the 's and count them in
method_missing to produce a length, call the method with that as a
parameter, returning a Note modified by the current key signature, and
then the +/-/~ would take over. Each vN function would be a “voice”
function that was later collected and combined by part(), so you could
handle multiple voice movement without a lot of crazy arrays and
stuff. Of course, you’d likely still want to allow [a
, +c_, e_] to
give you a chord in a particular voice for convenience.

Anyhow, just a thought, still a number of issues to be dealt with I’m
sure.

···

On Sun, 28 Sep 2003 17:31:23 +0900 “daz” dooby@d10.karoo.co.uk wrote:

Like Jim, my feeling is that some parsing will be
required. There’s no C# method in Ruby.

Allows for sharps, flats and naturals.


Ryan Pavlik rpav@mephle.com

“It’s at the expense of some of my favorite organs,
but it’s worth it.” - 8BT

What about an algebraic approach?

Kool idea. I like it.

···

On Sun, 2003-09-28 at 17:12, Joel VanderWerf wrote:

maybe

 clef do
   measure("4/4") do
    c + d + e + f + g + a + b + c + c*d*g
   end
 end

You could denote quarter notes with b/4 etc.

Dan Doel wrote:

Sabby and Tabby wrote:

(‘a’…‘g’).each {|x| eval <<“”}
def #{x}(*a)
@lines << if a.empty?
@lines[-1].unshift(‘#{x}’)
end

I didn’t realize that the first line didn’t count for here documents, so
you
could close blocks and such. I thought you would have needed:

(‘a’…‘g’).each {|x| eval <<“”
def #{x}(*a)
@lines << if a.empty?
@lines[-1].unshift(‘#{x}’)
end

}

You learn something new every day, I guess.

What? I’m confused. Are you saying that there are
two blank lines required at the end of this doc?

Personally I’ve never used “” as a terminator
anyway.

Hal

Martin DeMello wrote:

···

Sabby and Tabby sabbyxtabby@yahoo.com wrote:

Possible. But not for long.
notes = clef do
measure(“4/4”) do
c d e f g a b c
chord do
c e g
end
end
end

p notes

Beautiful. Pity it’s going away.

I agree. But “poetry mode” is not totally going away,
is it? Don’t we just get the warning in certain
circumstances?

What are the facts here?

Hal

Hal Fulton wrote:

What? I’m confused. Are you saying that there are
two blank lines required at the end of this doc?

Personally I’ve never used “” as a terminator
anyway.

No, just one. Anything more is Thunderbird making it hard for me to format
messages precisely.

  • Dan