You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

564 lines
22 KiB
Plaintext

4 years ago
(
var genWindow, masterView, faderView, buildGenerator, buildMetronome, buildTransport, buildAuxControls, buildMasterFader, buildTrackFader,
clockStringFunc, metronomeStringFunc, metronomeColorFunc, updateTransport;
4 years ago
buildGenerator = {arg view;
var ranSeed;
HLayout(
ranSeed = TextField(view, Rect(10, 10, 10, 20)).string_("20200525"),
Button(view).states_([["reset seed"]]).action_({ ranSeed.string = "20200525"}.inEnvir),
Button(view).states_([["random seed"]]).action_({ ranSeed.string = 50000000.rand.asString}.inEnvir),
Button(view).states_([["generate"]]).action_({
{~genAll.value(ranSeed.string.asInteger); ~appStatus.string = "status: ready"}.fork(AppClock);
~appStatus.string = "status: generating"}.inEnvir),
Button(view).states_([["transcribe"]]).action_({
{~transcribe.value(~scoreData, ~sectionData); ~appStatus.string = "status: ready"}.fork(AppClock);
~appStatus.string = "status: transcribing"}.inEnvir),
[~appStatus = StaticText(view).string_("status: ready"), stretch: 1], nil)
4 years ago
};
// these funcs update the elements of the transport panel
clockStringFunc = {
arg measure, beat;
var measureString, beatString, leadSpace;
measureString = measure.asInteger.asString;
beatString = beat.asInteger.asString;
leadSpace = (3 - measureString.size).collect({" "}).join;
leadSpace ++ measureString ++ "." ++ beatString
};
// [-30, -105, -104] and [-30, -105, -113] are unicode inverse bullet and normal bullet, respectively
metronomeStringFunc = { arg beat; if(beat == 1,
{[-30, -105, -104].collect({arg int; int.asAscii}).as(String)},
{[-30, -105, -113].collect({arg int; int.asAscii}).as(String)})};
metronomeColorFunc = { arg beat; if(beat == 1, {Color.red},{Color.black})};
updateTransport = {arg clock, metronome, measure, beat;
clock.string = clockStringFunc.value(measure, beat);
metronome.stringColor = metronomeColorFunc.value(beat);
metronome.string = metronomeStringFunc.value(beat);
{0.75.wait; {metronome.string = ""}.defer}.fork(~tempoClock, quant: 0);
}.inEnvir;
buildMetronome = {arg win;
var clock, metronome, layout;
clock = StaticText(win).string_(" 1.1").font_(Font("Liberation Mono", 200));
metronome = StaticText(win).string_([-30, -105, -104].collect({arg int; int.asAscii})
.as(String)).font_(Font("Liberation Mono", 300)).stringColor_(Color.red);
layout = HLayout(
clock,
StaticText(win).string_("|").font_(Font("Liberation Mono", 200)),
metronome
);
OSCFunc({ arg msg, time;
{
var measure, beat;
measure = msg[3].postln;
beat = msg[4];
updateTransport.value(clock, metronome, measure, beat)
}.inEnvir.defer;
},'/measureClock_' ++ ~hash, s.addr);
[clock, metronome, layout]
};
buildTransport = {arg win, view, metronome;
var sectionDisplay, updateSection, countOff;
4 years ago
HLayout(
Button(view).states_([["<<", Color.black]]).action_({arg pState; updateSection.value(-4)}.inEnvir),
Button(view).states_([["<", Color.black]]).action_({arg pState; updateSection.value(-1)}.inEnvir),
Button(view).states_([["play", Color.black], ["stop", Color.black, Color.grey]]).action_({arg pState;
if(pState.value == 1, {
{~patternProxy.source = Pseq(~patterns);
Pbind(
\instrument, \click_ ++ ~hash,
\beat, Pseq([1, 2, 1, 2]),
\dur, 1
).play(~tempoClock, quant: 0);
[1, 2, 1, 2].do({arg beat;
{
metronome.stringColor = metronomeColorFunc.value(beat);
metronome.string = metronomeStringFunc.value(beat);
}.defer;
0.75.wait;
{metronome.string = ""}.defer;
0.25.wait;
});
~patternProxy.play(~tempoClock, quant: 0)
}.fork(~tempoClock, quant: 0)
}, {
~patternProxy.stop
});
}.inEnvir),
4 years ago
Button(view).states_([[">", Color.black]]).action_({arg pState; updateSection.value(1)}.inEnvir),
Button(view).states_([[">>", Color.black]]).action_({arg pState; updateSection.value(4)}.inEnvir), nil,
sectionDisplay = StaticText(win).string_("section: 1.1").font_(Font("Liberation Mono", 70)), nil)
};
buildAuxControls = {arg view;
var tempo, address, updateSection;
HLayout(
tempo = TextField(view).string_("60"),
4 years ago
Button(view).states_([["set tempo"]]).action_({~tempoClock.tempo = tempo.string.asInteger / 60}.inEnvir),
StaticText(view).string_(" | "),
Button(view).states_([["auto advance", Color.black], ["auto advance", Color.black, Color.grey]]).action_({
arg v; ~autoAdvance = if(v.value == 0, {false}, {true});~autoAdvance;
}.inEnvir).value_(1),
Button(view).states_([["interludes", Color.black], ["interludes", Color.black, Color.grey]]).action_({
arg v; ~interludes = if(v.value == 0, {false}, {true})
}.inEnvir),
StaticText(view).string_(" | "),
address = TextField(view, Rect(10, 10, 10, 20)).string_("127.0.0.1:57120"),
Button(view).states_([["set address:port"]]).action_({
var addr, ip, port;
addr = address.string.split($:);
ip = addr[0];
port = addr[1].asInteger;
thisProcess.openUDPPort(port);
addr = NetAddr(ip, port);
OSCdef(\externalAdvance_ ++ ~hash, {arg msg, time; {updateSection.value(1)}.inEnvir.defer},'/nextSubsection', addr);
}.inEnvir),
[StaticText(view).string_(" "), stretch: 1])
};
buildMasterFader = {arg view, masterVol, masterMute, masterIndicators;
HLayout(
VLayout(
[HLayout(
Slider(view).value_(0.8).action_(
{arg v; masterVol = v.value * 1.25; ~play.set(\masterVol, masterVol)}.inEnvir),
masterIndicators[0],
masterIndicators[1]), stretch: 2],
Button(view).states_([["mute", Color.black], ["mute", Color.black, Color.grey]]).action_(
{arg v; masterMute = (1 - v.value).abs; ~play.set(\masterMute, masterMute)}.inEnvir),
StaticText(view).string_(" master ").align_(\center)
), nil)
};
buildTrackFader = {arg view, name, index, initVal;
var trackIndicator;
trackIndicator = LevelIndicator();
OSCFunc.new({arg msg; {trackIndicator.value = msg[3].ampdb.linlin(-50, 0, 0, 1)}.defer},
'/trackLevel_' ++ index ++ "_" ++ ~hash, s.addr);
HLayout(
VLayout(
HLayout(
Slider(view).value_(0.8).action_(
{arg v; var vol = v.value * 1.25; ~play.set(\vol_ ++ index, vol)}.inEnvir),
trackIndicator),
Button(view).states_([["mute", Color.black], ["mute", Color.black, Color.grey]]).action_(
{arg v; var mute = (1 - v.value).abs; ~play.set(\mute_ ++ index, mute)}.inEnvir).value_(initVal),
VLayout(
StaticText(view).string_("pan").align_(\center),
Knob(view).action_({arg v; var pan = v.value * 2 - 1; ~play.set(\pan_ ++ index, pan)}.inEnvir).value_(0.5)
), //[StaticText(view).string_(" "), stretch: 0.1]
StaticText(view).string_(name).align_(\center)
),
nil)
};
masterView = {arg win;
var updateTransport, updateSection,
view, generatorLayout, clock, metronome, metronomeLayout, transportLayout, auxControlsLayout, countOff, ranSeed, order, tempo, sectionDisplay, address;
4 years ago
view = View(win);
generatorLayout = buildGenerator.value(view);
# clock, metronome, metronomeLayout = buildMetronome.value(win);
transportLayout = buildTransport.value(win, view, metronome);
auxControlsLayout = buildAuxControls.value(view);
4 years ago
view.layout_(
HLayout(
[
VLayout(
metronomeLayout,
4 years ago
[StaticText(view).string_(" "), stretch: 1], //nil
transportLayout,
4 years ago
[StaticText(view).string_(" "), stretch: 1],
auxControlsLayout,
4 years ago
[StaticText(view).string_(" "), stretch: 1],
generatorLayout),
4 years ago
alignment: \top
]
)
)
};
faderView = {arg win;
var view, masterIndicators, trackIndicators, master, tracks;
var partAbbr = ["*", "III", "II", "I", "accomp", "click"];
var trackNames = ["*", "III", "II", "I", "accomp", "click"];
var partVols, partMutes, partPans;
var masterMute, masterVol;
// set initial mixer values
partVols = [1, 1, 1, 1, 1, 1];
partMutes = [0, 1, 1, 1, 1, 0];
partPans = [0, 0, 0, 0, 0, 0];
masterMute = 1;
masterVol = 1;
view = View(win);
masterIndicators = {LevelIndicator()} ! 2;
trackIndicators = {LevelIndicator()} ! 6;
/*
OSCFunc.new({arg msg; {
{arg i; masterIndicators[i].value = msg[3 + i].ampdb.linlin(-40, 0, 0, 1)} ! 2}.defer},
'/masterLevels' ++ ~hash, s.addr);
OSCFunc.new({arg msg; {
{arg i; trackIndicators[i].value = msg[3 + i].ampdb.linlin(-40, 0, 0, 1)} ! 6}.defer},
'/trackLevels' ++ ~hash, s.addr);
*/
master = buildMasterFader.value(view, masterVol, masterMute, masterIndicators);
tracks = {arg part;
buildTrackFader.value(view, trackNames[part], part, 0);
} ! 6;
view.layout_(HLayout(master, nil, *tracks))
};
~generateGUI = {
var win, tabButtonReset, transportButton, mixerButton, helpButton, tabs;
win = Window("to kill a monarch", Rect(500, 500, 1100, 575), false).front;
tabButtonReset = {transportButton.value = 1; mixerButton.value = 1; helpButton.value = 1};
win.layout = VLayout(
HLayout(
HLayout(
[
transportButton = Button().states_([["transport", Color.white, Color.grey], ["transport", Color.black]]).action_(
{tabButtonReset.value; transportButton.value = 0; tabs.index = 0 }.inEnvir).value_(0), stretch: 1
], [
mixerButton = Button().states_([["mixer", Color.white, Color.grey], ["mixer", Color.black]]).action_(
{tabButtonReset.value; mixerButton.value = 0; tabs.index = 1 }.inEnvir).value_(1), stretch: 1
]
),
helpButton = Button().states_([["help", Color.white, Color.grey], ["help", Color.black]]).action_(
{tabButtonReset.value; helpButton.value = 0; tabs.index = 2 }.inEnvir).value_(1)
),
tabs = StackLayout(masterView.value(win), faderView.value(win)/*, helpView.value*/));
};
//~generateGUI.value
//~~FUNCTION THAT GENERATES THE GUI
/*
~generateGUI = {
var win, clockStringFunc, metronomeStringFunc, metronomeColorFunc, masterView, faderView, helpView, tabs;
var tabButtonReset, transportButton, mixerButton, helpButton, startPos = 0;
var partAbbr = ["guitar", "accompHigh", "accompLowLower", "accompLowUpper", "interlude", "click"];
var trackNames = ["guitar", "high", "low 1", "low 2", "interlude", "click"];
var partVols, partMutes, partPans;
var masterMute, masterVol;
// set initial mixer values
partVols = [1, 1, 1, 1, 1, 1];
partMutes = [0, 1, 1, 1, 1, 0];
partPans = [0, 0, 0, 0, 0, 0];
masterMute = 1;
masterVol = 1;
// these funcs update the elements of the transport panel
clockStringFunc = {
arg measure, beat;
var measureString, beatString, leadSpace;
measureString = measure.asInteger.asString;
beatString = beat.asInteger.asString;
leadSpace = (3 - measureString.size).collect({" "}).join;
leadSpace ++ measureString ++ "." ++ beatString
};
// [-30, -105, -104] and [-30, -105, -113] are unicode inverse bullet and normal bullet, respectively
metronomeStringFunc = { arg beat; if(beat == 1,
{[-30, -105, -104].collect({arg int; int.asAscii}).as(String)},
{[-30, -105, -113].collect({arg int; int.asAscii}).as(String)})};
metronomeColorFunc = { arg beat; if(beat == 1, {Color.red},{Color.black})};
win = Window("Counterfeiting in Colonial Connecticut", Rect(500, 500, 1100, 575), false).front;
masterView = {
var updateTransport, updateSection,
view, generator, transport, countOff, ranSeed, order, tempo, sectionDisplay, clock, metronome, address;
// this func updates the whole transport panel
updateTransport = {arg measure, beat;
clock.string = clockStringFunc.value(measure, beat);
metronome.stringColor = metronomeColorFunc.value(beat);
metronome.string = metronomeStringFunc.value(beat);
{0.75.wait; {metronome.string = ""}.defer}.fork(~tempoClock, quant: 0);
}.inEnvir;
// this func handles the movement between sections
updateSection = {arg shift, stop = true, manualCall = true;
var runThis;
runThis = (manualCall || (manualCall.not && ~autoAdvance));
runThis = runThis && ((~currentSection + shift) < ~sectionOrder.size);
runThis = runThis && (((~currentSection % 4) == 3) && ~interludes && manualCall.not).not;
if(runThis, {
var truncOnly, section, subSection;
if(~isPlaying, {
if(stop, {
~patterns[~sectionOrder[~currentSection]].stop
})
});
truncOnly = case
{(~currentSection + shift) < 0} {true}
{(shift < 0) && ~isPlaying} {true}
{(shift < -1) && ((~currentSection % 4) > 0)} {true}
{true} {false};
if(truncOnly.not, {
~currentSection = (~currentSection + shift).trunc(shift.abs);
}, {
~currentSection = ~currentSection.trunc(shift.abs);
});
section = ((~sectionOrder[~currentSection] / 4) + 1).asInteger;
subSection = ((~sectionOrder[~currentSection] % 4) + 1).asInteger;
sectionDisplay.string = "section: " ++ section.asString ++ "." ++ subSection.asString;
if(~isPlaying, {
countOff = {
if(~interludes && ((~currentSection % 4) == 0), {
Pbind(
\instrument, \click ++ ~hash,
\beat, Pseq([1, 2, 1, 2]),
\dur, 1
).play(~tempoClock, quant: 0);
[1, 2, 1, 2].do({arg beat;
{
metronome.stringColor = metronomeColorFunc.value(beat);
metronome.string = metronomeStringFunc.value(beat);
}.defer;
0.75.wait;
{metronome.string = ""}.defer;
0.25.wait;
});
});
~play.set(\sel, ~currentSection % 2);
~patterns[~sectionOrder[~currentSection]].play(~tempoClock, quant: 0);
if(~interludes && ((~currentSection % 4) == 3) && (~currentSection != (~sectionOrder.size - 1)), {
var center, interval, freq1, freq2, tremRate;
center = 50 - 12.0.rand;
interval = 3.0.rand + 2;
freq1 = (center + (interval / 2)).midicps;
freq2 = (center - (interval / 2)).midicps;
tremRate = 50 + 4.0.rand2;
~interludeTremelo.set(\gate, 1, \amp, 1, \freq1, freq1, \freq2, freq2, \tremRate, tremRate);
});
if((~currentSection % 4) == 0, {
~interludeTremelo.set(\gate, 0);
});
if(((~currentSection % 4)) != 0 && ((~currentSection % 4) != 3), {
~interludeTremelo.set(\gate, 0, \amp, 0);
});
}.fork(~tempoClock, quant: 0);
}, {
var measure, beat;
measure = ~sectionStartMeasure[~sectionOrder[~currentSection]];
beat = 1;
updateTransport.value(measure, beat);
});
});
}.inEnvir;
// these funcs receive messages from the synth
OSCFunc({ arg msg, time;
{
var measure, beat;
measure = msg[3];
beat = msg[4];
updateTransport.value(measure, beat)
}.inEnvir.defer;
},'/measureClock' ++ ~hash, s.addr);
OSCFunc({ arg msg, time; {updateSection.value(1, false, false)}.inEnvir.defer},'/nextSubsection' ++ ~hash, s.addr);
OSCdef(\externalAdvance ++ ~hash, {arg msg, time; {updateSection.value(1)}.inEnvir.defer},'/nextSubsection', s.addr);
view = View(win);
generator = HLayout(
ranSeed = TextField(view, Rect(10, 10, 10, 20)).string_("20200525"),
Button(view).states_([["reset seed"]]).action_({ ranSeed.string = "20200525"}.inEnvir),
Button(view).states_([["random seed"]]).action_({ ranSeed.string = 50000000.rand.asString}.inEnvir),
Button(view).states_([["generate"]]).action_({
{~genAll.value(ranSeed.string.asInteger); ~appStatus.string = "status: ready"}.fork(AppClock);
~appStatus.string = "status: generating"}.inEnvir),
Button(view).states_([["transcribe"]]).action_({
{~transcribe.value(~scoreData, ranSeed.string); ~appStatus.string = "status: ready"}.fork(AppClock);
~appStatus.string = "status: transcribing"}.inEnvir),
[~appStatus = StaticText(view).string_("status: ready"), stretch: 1], nil);
transport = HLayout(
Button(view).states_([["<<", Color.black]]).action_({arg pState; updateSection.value(-4)}.inEnvir),
Button(view).states_([["<", Color.black]]).action_({arg pState; updateSection.value(-1)}.inEnvir),
Button(view).states_([["play", Color.black], ["stop", Color.black, Color.grey]]).action_(
{arg pState;
if(pState.value == 0, {
var measure, beat;
countOff.stop;
~isPlaying = false;
~patterns[~sectionOrder[~currentSection]].stop;
~interludeTremelo.set(\gate, 0);
measure = ~sectionStartMeasure[~currentSection];
beat = 1;
updateTransport.value(measure, beat);
~interludeTremelo.set(\gate, 0, \amp, 0);
},{
countOff = {
Pbind(
\instrument, \click ++ ~hash,
\beat, Pseq([1, 2, 1, 2]),
\dur, 1
).play(~tempoClock, quant: 0);
[1, 2, 1, 2].do({arg beat;
{
metronome.stringColor = metronomeColorFunc.value(beat);
metronome.string = metronomeStringFunc.value(beat);
}.defer;
0.75.wait;
{metronome.string = ""}.defer;
0.25.wait;
});
~isPlaying = true;
~play.set(\sel, ~currentSection % 2);
~patterns[~sectionOrder[~currentSection]].play(~tempoClock, quant: 0);
if(~interludes && ((~currentSection % 4) == 3) && (~currentSection != (~sectionOrder.size - 1)), {
var center, interval, freq1, freq2, tremRate;
center = 50 - 12.0.rand;
interval = 3.0.rand + 2;
freq1 = (center + (interval / 2)).midicps;
freq2 = (center - (interval / 2)).midicps;
tremRate = 50 + 4.0.rand2;
~interludeTremelo.set(\gate, 1, \amp, 1, \freq1, freq1, \freq2, freq2, \tremRate, tremRate);
});
}.fork(~tempoClock, quant: 0);
})
}.inEnvir
),
Button(view).states_([[">", Color.black]]).action_({arg pState; updateSection.value(1)}.inEnvir),
Button(view).states_([[">>", Color.black]]).action_({arg pState; updateSection.value(4)}.inEnvir), nil,
sectionDisplay = StaticText(win).string_("section: 1.1").font_(Font("Liberation Mono", 70)), nil);
view.layout_(HLayout(
[VLayout(
HLayout(clock = StaticText(win).string_(" 1.1").font_(Font("Liberation Mono", 200)),
StaticText(win).string_("|").font_(Font("Liberation Mono", 200)),
metronome = StaticText(win).string_([-30, -105, -104].collect({arg int; int.asAscii}).as(String)).font_(Font("Liberation Mono", 300)).stringColor_(Color.red)),
nil, transport, nil,
HLayout(
tempo = TextField(view).string_("90"),
Button(view).states_([["set tempo"]]).action_({~tempoClock.tempo = tempo.string.asInteger / 60}.inEnvir),
StaticText(view).string_(" | "),
Button(view).states_([["auto advance", Color.black], ["auto advance", Color.black, Color.grey]]).action_({
arg v; ~autoAdvance = if(v.value == 0, {false}, {true});~autoAdvance;
}.inEnvir).value_(1),
Button(view).states_([["interludes", Color.black], ["interludes", Color.black, Color.grey]]).action_({
arg v; ~interludes = if(v.value == 0, {false}, {true})
}.inEnvir),
StaticText(view).string_(" | "),
address = TextField(view, Rect(10, 10, 10, 20)).string_("127.0.0.1:57120"),
Button(view).states_([["set address:port"]]).action_({
var addr, ip, port;
addr = address.string.split($:);
ip = addr[0];
port = addr[1].asInteger;
thisProcess.openUDPPort(port);
addr = NetAddr(ip, port);
OSCdef(\externalAdvance ++ ~hash, {arg msg, time; {updateSection.value(1)}.inEnvir.defer},'/nextSubsection', addr);
}.inEnvir),
[StaticText(view).string_(" "), stretch: 1]),
[StaticText(view).string_(" "), stretch: 1],
HLayout(
order = TextField(view).string_("1-16"),
Button(view).states_([["set order"]]).action_({
~patterns[~sectionOrder[~currentSection]].stop;
~sectionOrder = order.string.split($,).collect({arg secEntry;
var bounds;
bounds = secEntry.split($-).collect({arg item; item.asInteger - 1});
((bounds.minItem)..(bounds.maxItem)).collect({arg sec;
(sec.asInteger * 4) + [0, 1, 2, 3]
});
}).flat;
~currentSection = 0;
updateSection.value(0);
}.inEnvir),
[StaticText(view).string_(" "), stretch: 1]),
[StaticText(view).string_(" "), stretch: 1], generator
), alignment: \top]))};
faderView = {
var view, masterIndicators, trackIndicators, master, tracks;
view = View(win);
masterIndicators = {LevelIndicator()} ! 2;
trackIndicators = {LevelIndicator()} ! 6;
OSCFunc.new({arg msg; {
{arg i; masterIndicators[i].value = msg[3 + i].ampdb.linlin(-40, 0, 0, 1)} ! 2}.defer},
'/masterLevels' ++ ~hash, s.addr);
OSCFunc.new({arg msg; {
{arg i; trackIndicators[i].value = msg[3 + i].ampdb.linlin(-40, 0, 0, 1)} ! 6}.defer},
'/trackLevels' ++ ~hash, s.addr);
master = HLayout(
VLayout(
[HLayout(
Slider(view).value_(0.8).action_(
{arg v; masterVol = v.value * 1.25; ~play.set(\masterVol, masterVol)}.inEnvir),
masterIndicators[0],
masterIndicators[1]), stretch: 2],
Button(view).states_([["mute", Color.black], ["mute", Color.black, Color.grey]]).action_(
{arg v; masterMute = (1 - v.value).abs; ~play.set(\masterMute, masterMute)}.inEnvir),
StaticText(view).string_(" master ").align_(\center)
), nil);
tracks = {arg part;
HLayout(
VLayout(
HLayout(
Slider(view).value_(0.8).action_(
{arg v; partVols[part] = v.value * 1.25; ~play.set(partAbbr[part] ++ "Vol", partVols[part])}.inEnvir),
trackIndicators[part]),
Button(view).states_([["mute", Color.black], ["mute", Color.black, Color.grey]]).action_(
{arg v; partMutes[part] = (1 - v.value).abs; ~play.set(partAbbr[part] ++ "Mute", partMutes[part])}.inEnvir).value_(
{if((part == 0) || (part == 5), {1}, {0})}.value),
StaticText(view).string_("pan").align_(\center),
Knob(view).value_(0.5).action_(
{arg v; partPans[part] = v.value * 2 - 1; ~play.set(partAbbr[part] ++ "Pan", partPans[part])}.inEnvir),
StaticText(view).string_(trackNames[part]).align_(\center)
),
nil)
} ! 6;
view.layout_(HLayout(master, nil, *tracks))};
helpView = {
TextView(win).string_(File.readAllString(~dir +/+ "cicc_readme.scd")).editable_(false);
};
tabButtonReset = {transportButton.value = 1; mixerButton.value = 1; helpButton.value = 1};
win.layout = VLayout(
HLayout(
HLayout(
[
transportButton = Button().states_([["transport", Color.white, Color.grey], ["transport", Color.black]]).action_(
{tabButtonReset.value; transportButton.value = 0; tabs.index = 0 }.inEnvir).value_(0), stretch: 1
], [
mixerButton = Button().states_([["mixer", Color.white, Color.grey], ["mixer", Color.black]]).action_(
{tabButtonReset.value; mixerButton.value = 0; tabs.index = 1 }.inEnvir).value_(1), stretch: 1
]
),
helpButton = Button().states_([["help", Color.white, Color.grey], ["help", Color.black]]).action_(
{tabButtonReset.value; helpButton.value = 0; tabs.index = 2 }.inEnvir).value_(1)
),
tabs = StackLayout(masterView.value, faderView.value, helpView.value));
};
*/
)