Mapping notes to strings for a drawFunc

I have this drawFunc that allows me to draw some strings on a Rect:

w.drawFunc = {
    ~words.next.drawCenteredIn( Rect(0, 0, 1440, 900) );
    Pen.strokeRect(Rect(0, 0, 1440, 900))
};

I run it from a Pbind that simply refreshes that. Now, rather than having a static sequence of words stored in ~words, i’d like to get something on the fly from some other running process. Say, to put an easy example, the midi notes of some sequence. What’s a good way to map those midi notes to strings, so that note 40 draws “foo”, 41 draws “bar”, and so on?
I am totally lost so any pointers will be appreciated.

Best wishes

I don’t know all the variables involved in your particular scenario, but here’s a simple example that may help as a starting point.

Below, a single note is mapped to a single word from a list. For this example, I assume that the number of words is not much more than the number of ‘usable’ midi notes (what is ‘usable’ will vary for each person and project, but for the sake of the example I use here 93 notes, slightly more than the 88-note range of a piano.)

~words = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi et suscipit diam, lobortis molestie sapien. Nam nunc ligula, maximus sit amet tristique vitae, rhoncus ut lorem. Nulla ac ipsum ipsum. Praesent in justo augue. Donec rutrum lacus placerat nulla tincidunt feugiat. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean condimentum, leo id pharetra consequat, libero erat consectetur dolor, id venenatis enim mauris eu felis. Pellentesque ac eleifend erat. Nulla ut fermentum purus. Cras condimentum aliquam finibus. Aliquam erat volutpat. Vestibulum lacinia magna id nulla pharetra feugiat. Ut tristique mi magna.".split($ );

~words.size; // 93 words; 

// if mapping one word to one note, 93 notes is almost 8 octaves.

(
var offset = 21; // start from lowest note of piano
p = Pbind(
	\midinote, Pwhite(0, 92) + offset,
	\dur, Pwhite(0.2, 0.5),
	\amp, Pwhite(0.1, 0.4),
	\word, Pfunc({ |e| 
		var index = e[\midinote] - offset;
		~words[index].postln;
	}),
).play;
)

that works and i love the simplicity of it. however i was hoping i could print those words on a regular window rather than the post window. is there a way to use that Pfunc inside of one of these below? it’s the simplified version of what i was using, but i’m still very novice when it comes to dealing with graphics here (as if it wasn’t obvious). in my example below the word printed comes from a fixed Pseq, so it’s a predetermined stream. i’d like to plug those on the fly from current \midinote on a Pbind, though. any pointers appreciated!!

(
~mywords = Pseq(["hello", "world", "yes", "no"], inf).asStream;
~colors1 = Pseq([Color.new255(156, 110, 30), Color.new255(110, 12, 11)], inf).asStream;

w = Window(" ", Rect(0, 0, 444, 444));
w.front;

r = Rect(0, 0, 444, 444);

w.drawFunc = {
    ~mywords.next.drawCenteredIn(
        r
    );
    Pen.strokeRect(r);
w.view.background_(~colors1.next);

};

~graf = Pbind(\out, 99,
        \dur, 1,
        \xyx, Pfunc { { w.refresh }.defer(~offset) }
).play;
)```

Right, and IMO it would be cleaner to have all the calculation in the Pbind, rather than dividing up the note and words calculations into different places.

It’s a little awkward with a drawFunc that you can’t pass any arguments into it. Passing arguments is the normal way to run an action, where the action depends on some information provided at runtime. But that isn’t supported for drawFunc, so we need another way.

I often/usually find it helpful to think of a GUI as displaying or updating the state of some other object – actually, this is relevant for most GUI situations, but it’s a nice way out of the no-argument-passing limitation here.

Instead of “Pbind → GUI,” think of:

  • Pbind → storage object
  • GUI ← storage object

We often use Pdefn as a storage object (see any number of threads about dynamically updating the contents of a Pbind).

(
Pdefn(\word, "nothing yet");
Pdefn(\color, Color.rand);

// these streams aren't "calculation" streams --
// only a convenient way to get the current value from the Pdefn-as-storage object
~wordStream = Pdefn(\word).asStream;
~colorStream = Pdefn(\color).asStream;

w = Window(" ", Rect(0, 0, 444, 444));
w.front;

// for convenience:
w.onClose = { ~graf.stop };

r = Rect(0, 0, 444, 444);

w.drawFunc = {
	~wordStream.next.drawCenteredIn(r);
	Pen.strokeRect(r);
	w.view.background_(~colorStream.next);
};

~graf = Pbind(
	\dur, 1,
	\midinote, Pwhite(48, 72, inf),
	\word, Pindex(
		["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"],
		Pkey(\midinote) % 12
	),
	\color, Pfunc { Color( * Array.fill(3, { rrand(0.6, 1) })) },
	\xyx, Pfunc { |ev|
		// put the things into storage
		Pdefn(\word, ev[\word]);
		Pdefn(\color, ev[\color]);
		// then update the window
		{ w.refresh }.defer(s.latency);
	}
).play;
)

hjh

3 Likes