PlayerSeqTrack
Play a series of Players in a sequence, with individual control over duration,
attack,release and level.
This is only designed to work with Players that already saved to disk.
The primary design difference between this and CyclePlayers is that
PlayerSeqTrack does not need to know all of its players before play/editing.
They can be added and removed dynamically, even while playing.
PlayerSeqTrack( arrayOfPlayerPaths, arrayOfBeatDurations,arrayOfEnvelopes, loop)
arrayOfPlayerPaths - or nil (players can be added later )
or they can be actual players. if you use paths, then identical paths
in the array will play the identical player (only one copy is loaded).
so the sequence can contain repeats:
[ path1, path2, path1, path2, path3 ...]
(
// locate some players on your hard drive and add them to a list
// hit cancel when you have enough
l = List.new;
f = {
GetFileDialog({ arg ok,path;
if(ok,{
l.add(loadPath(path));
f.value;
})
});
};
f.value
)
(
p = PlayerSeqTrack.new;
6.do({
p.insertLast(Properties(\working).wchoose.asString.loadDocument);
});
p.setDuration(0,4);
p.setDuration(1,4);
p.setDuration(2,4);
p.setDuration(3,4);
p.setDuration(4,4);
p.setDuration(5,4);
p.gui
)
p.play;
p.free;
)
p.insp
PlayerSeqTrackGui
now while watching the pretty visuals:
(
p.gui;
p.changed; // update the gui
)
// while playing is fine, weve already loaded the players
p.insert( 3, l.choose ).changed;
p.insert( 8.rand, l.choose ).changed;
(
5.do({ arg i;
p.insert(i, l.choose );
});
p.changed;
)
(
5.do({ arg i;
p.setDuration( p.playerSeq.size.rand, [4,8,16,32].choose );
});
p.changed;
)
(
5.do({ arg i;
p.setRelease( i, rrand(0.01,5.0) );
});
// no gui display of release times
)
(
5.do({ arg i;
p.setLevel( i, rrand(0.1,2.0) );
});
// no gui display of levels
)
p.deleteAt(6).changed;
Note that the duration display changes also. It also changes when you change the tempo.
GUI Hot-Keys
While selected on any of the sequences these keys are active:
<- select previous
-> select next
opt -> move selected left
opt <- move selected right
up increase dur by 1 bar
down decrease dur by 1 bar
opt-up double duration
opt-down half duration
shift-opt-up double durations of all steps
shift-opt-down half durations of all steps
` relocate (while playing) to this step
delete delete this step
g gui the player at this step
i open information window
escape focus on the first focusable view in this window that is not one of the sequence steps
The information window
in the information window you can edit
duration, attack,decay,level, envelope
in three selectable scopes:
this step, all steps with this player, all steps
It is also possible to embed the info window on the same layout:
(
Sheet({ arg f;
PlayerSeqTrack.new.topGui(f).infoGui(f);
})
)
Note that topGui returns the PlayerSeqTrackGui object, which responds to infoGui(layout);
Its turtles all the way down
It is of course possible to put a PlayerSeqTrack inside of another PlayerSeqTrack.
Multiple tracks are obtainable via the use of PlayerMixer, though they won't easily
remained synchronized if you relocate while playing.
And the gui will be not lined up verticle.
Eventually i will write a multi-track version that holds and synchronizes multiple PlayerSeqTrack.
Live Insert of Players
( // insert players at the selected step, even while playing
Sheet({ arg f;
p = PlayerSeqTrack.new;
g = p.topGui(f); // return the gui itself
Label(f,"insert:");
l.do({ arg player;
ActionButton(f,player.name,{ p.insert(g.selected ? 0 , player).changed })
})
})
)
(
// insert players NOW at the presently playing step
// 808 style
Sheet({ arg f;
p = PlayerSeqTrack.new;
p.topGui(f);
Label(f,"insertNow:");
l.do({ arg player;
ActionButton(f,player.name,{ p.insertNow( player,round: 1.0).changed })
})
})
)
insert(step,player,duration,env)
step- index to insert at
player - the player object or path to insert
in the example above, i used the actual player because its faster, you share
the same player instance, and it doesn't have to load anything from disk while
its playing.
duration - the number of beats it should play for
if nil,
use the duration of any previous appearance of this player in the sequence
if that is nil,
use the natural beatDuration of the player
if that is nil,
use 128.0 beats
env - the envelope to use
if nil,
use the envelope of any previous appearance of this player in the sequence
if that is nil,
use default envelope
All players that you insert must have a path (must have been loaded from disk).
When this example saves, notice that the steps 0 and 3 repeat the same player. On reloading, they will
share the same copy.
PlayerSeqTrack.new(
[ ":Patches:footfist:dawhohop", ":Patches:footfistwhisker:dhallooo", ":Patches:footfist:dawhohop",
":Patches:footfistwhisker:buggercraft", ":Patches:footfistwhisker:basscl", ":Patches:footfist:simp" ],
[ 16, 16, 16, 16, 16, 16 ],
[ Env.new([ 0, 1, 1, 0 ], [ 0.01, 1, 0.1 ], [ -2, -2, -2 ], 2, nil), Env.new([ 0, 1, 1, 0 ], [ 0.01, 1, 0.1 ], [ -2, -2, -2 ], 2, nil), Env.new([ 0, 1, 1, 0 ], [ 0.01, 1, 0.1 ], [ -2, -2, -2 ], 2, nil), Env.new([ 0, 1, 1, 0 ], [ 0.01, 1, 0.1 ], [ -2, -2, -2 ], 2, nil), Env.new([ 0, 1, 1, 0 ], [ 0.01, 1, 0.1 ], [ -2, -2, -2 ], 2, nil), Env.new([ 0, 1, 1, 0 ], [ 0.01, 1, 0.1 ], [ -2, -2, -2 ], 2, nil) ]
)
Actually PlayerSeqTrack could play players without a path, but the gui would display them
all as "nil" "nil" etc.
And it would save as something like this:
PlayerSeqTrack.new(
[ Patch.new(
[ 'minimoog', 'detune' ],
[ 440, -4, 0, 0.4, 1 ]
), Patch.new(
[ 'synths', 'stereo', 'SyncSaw' ],
[ BeatClockPlayer(16)
, 440, 447.214, 0.5, 0.4, Env.new([ 0, 1, 0.5, 0 ], [ 0.01, 0.3, 1 ], -4, 2, nil), 4, 2 ]
), Patch.new(
[ 'minimoog', 'detune' ],
[ 440, -4, 0, 0.4, 1 ]
) ],
[ 16, 16, 16 ],
[ Env.new([ 0, 1, 1, 0 ], [ 0.01, 1, 0.1 ], [ -2, -2, -2 ], 2, nil), Env.new([ 0, 1, 1, 0 ], [ 0.01, 1, 0.1 ], [ -2, -2, -2 ], 2, nil), Env.new([ 0, 1, 1, 0 ], [ 0.01, 1, 0.1 ], [ -2, -2, -2 ], 2, nil) ]
)
And on reload the identical steps 0 and 3 would not be able to share the same copy.