Here is a keyboard to capture a little phrase for the degree and dur for the default Synthdef using the Chromatic scale. Please, Tell me if anything is off.
Use this to test it;
(
Pbind(
\scale, Scale.chromatic.degrees,
\degree, Pseq([8, 7, 1, 1, 3, 0],1),
\dur, Pseq([0.606, 0.783, 1.229, 0.799, 0.836, 0.814],1)).play;
)
(
// Function to create our keyboard
~createKeyboard = {
var startTime;
// Define a simple synth with an envelope
SynthDef(\keyboardSynth, { |out=0, freq=440, amp=0.5, gate=1|
var sig, env;
env = EnvGen.kr(Env.adsr(0.01, 0.1, 0.5, 0.1), gate, doneAction: 2);
sig = SinOsc.ar(freq) * env * amp;
Out.ar(out, sig ! 2);
}).add;
// Create a window for the keyboard
~win = Window("Two-Octave Chromatic Keyboard with Timing", Rect(100, 100, 1000, 450)).front;
// Define note names and their corresponding chromatic scale degrees for two octaves
~notes = [\C, \Cs, \D, \Ds, \E, \F, \Fs, \G, \Gs, \A, \As, \B,
\C, \Cs, \D, \Ds, \E, \F, \Fs, \G, \Gs, \A, \As, \B];
~scaleDegrees = (0..23); // 0 to 11 for first octave, 12 to 23 for second octave
// Array to store the sequence of played notes with timing
~sequence = List[];
// Timing capture flag
~capturingTime = false;
// Create buttons for each note
~buttons = ~notes.collect({ |note, i|
var button = Button(~win, Rect(10 + (i * 40), 10, 35, 100))
.states_([[note.asString, Color.black, Color.white], [note.asString, Color.white, Color.black]])
.action_({ |but|
var freq = (60 + i).midicps;
var synth = Synth(\keyboardSynth, [\freq, freq, \amp, 0.5]);
if(~capturingTime, {
var elapsedTime = Main.elapsedTime - startTime;
~sequence.add([~scaleDegrees[i], elapsedTime.round(0.001)]);
}, {
~sequence.add([~scaleDegrees[i], nil]);
});
but.value = 1;
AppClock.sched(0.2, {
but.value = 0;
synth.set(\gate, 0);
nil
});
});
});
// Create a text field to display the sequence
~seqDisplay = TextView(~win, Rect(10, 120, 980, 100))
.string_("Played sequence: ")
.editable_(false);
// Create a button to clear the sequence
Button(~win, Rect(10, 230, 100, 30))
.states_([["Clear Sequence"]])
.action_({
~sequence.clear;
~seqDisplay.string_("Played sequence: ");
});
// Create a button to print the sequence
Button(~win, Rect(120, 230, 100, 30))
.states_([["Print Sequence"]])
.action_({
var noteArray, durArray;
noteArray = ~sequence.collect({ |item| item[0] });
durArray = ~sequence.collect({ |item, i|
if(item[1].isNil, { 0.5 }, {
if(i == 0, { item[1] }, { item[1] - ~sequence[i-1][1] })
}).round(0.001);
});
"Note Array:".postln;
noteArray.postln;
"Duration Array:".postln;
durArray.postln;
"Pbind pattern:".postln;
("Pbind(\\degree, " ++ noteArray.asCompileString ++
", \\dur, " ++ durArray.asCompileString ++ ")").postln;
});
// Create a button to play the sequence
Button(~win, Rect(230, 230, 100, 30))
.states_([["Play Sequence"]])
.action_({
Routine({
var prevTime = 0;
~sequence.do({ |item|
var degree = item[0];
var time = item[1];
var freq = (degree + 60).midicps;
var synth;
if(time.notNil, {
(time - prevTime).wait;
prevTime = time;
}, {
0.5.wait;
});
synth = Synth(\keyboardSynth, [\freq, freq, \amp, 0.5]);
AppClock.sched(0.2, { synth.set(\gate, 0); nil });
});
}).play;
});
// Create a button to start/stop timing capture
~timingButton = Button(~win, Rect(340, 230, 150, 30))
.states_([
["Start Timing Capture", Color.black, Color.green],
["Stop Timing Capture", Color.white, Color.red]
])
.action_({ |but|
if(but.value == 1, {
~capturingTime = true;
startTime = Main.elapsedTime;
}, {
~capturingTime = false;
});
});
// Update sequence display function
~updateSeqDisplay = {
~seqDisplay.string_("Played sequence: " ++ ~sequence.collect({ |item|
var note = item[0];
var time = item[1];
if(time.isNil, {
note.asString
}, {
note.asString ++ "@" ++ time.round(0.001).asString
});
}).join(", "));
};
// Set up a routine to periodically update the sequence display
Routine({
loop {
~updateSeqDisplay.();
0.1.wait;
}
}).play(AppClock);
};
// Check if server is running, boot if necessary, then create the keyboard
if(s.serverRunning.not) {
s.waitForBoot({
~createKeyboard.value;
});
} {
~createKeyboard.value;
};
)