Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions App/GeneratorWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,15 @@ class GeneratorPanel : public juce::Component
note.setText (musical
? "Puts track 1 in drum mode and replaces its drum patterns 1-5 and 16, patterns 1-12 and 16 of tracks 2-4 (energy levels by "
"chord; 16 is empty) and scenes 1-8: intro, groove, drop, drop variation, breakdown, build, peak, outro. Each scene chains four "
"patterns per track. The KeyStep does not change scenes by itself: you play them live. Drums use the default drum map "
"patterns per track. Tracks 2-4 go to the sequencer (Seq) and the scene mutes are cleared. The KeyStep does not change scenes by itself: "
"you play them live. Drums use the default drum map "
"and reach the track 1 instrument on the drum channel (10). Nothing reaches the KeyStep until you load it (Write to slot)."
: st != nullptr
? "Puts track 1 in drum mode and replaces its drum patterns 1-4, patterns 1-4 of tracks 2-4 (bass, chords, lead; their time "
"division and Mono/Poly too) and scenes 1-4. Drums use the default drum map (kick 36, snare 38, clap 39, hats 42 and 46) and "
"division and Mono/Poly too) and scenes 1-4; tracks 2-4 go to the sequencer (Seq) and those scenes lose their mutes. Drums use the default drum map (kick 36, snare 38, clap 39, hats 42 and 46) and "
"reach the track 1 instrument on the drum channel (10). Nothing reaches the KeyStep until you load it (Write to slot)."
: "Replaces patterns 1-4 of the four tracks and scenes 1-8 of the project shown (and the playback settings of those patterns). It does "
"not touch drums, the arpeggiator or other patterns. Nothing reaches the KeyStep until you load it (Write to slot).",
: "Replaces patterns 1-4 of the four tracks and scenes 1-8 of the project shown (and the playback settings of those patterns), puts the "
"tracks in the sequencer (Seq, not Arp or Drum) and clears the mutes of those scenes. Other patterns are left alone. Nothing reaches the KeyStep until you load it (Write to slot).",
juce::dontSendNotification);
}

Expand Down
16 changes: 13 additions & 3 deletions App/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,7 @@ class MainComponent : public juce::Component, private juce::Timer
octUp.onClick = [this] { lowPitch = std::min (96, lowPitch + 12); showPattern(); };
projectBox.onChange = [this] { selectProject(); };
trackTabs.onSelect = [this] (int t) { trackBox.setSelectedId (t, juce::sendNotification); };
trackBox.onChange = [this] { trackTabs.setSelected (trackBox.getSelectedId(), false); grid.setTrack (track() - 1); refreshCards(); recChannelBox.setSelectedId (track(), juce::dontSendNotification); loadPattern(); };
trackBox.onChange = [this] { trackTabs.setSelected (trackBox.getSelectedId(), false); grid.setTrack (track() - 1); refreshCards(); loadPattern(); recChannelBox.setSelectedId (engine.channel (track() - 1), juce::dontSendNotification); };
patternBox.onChange = [this] { loadPattern(); };
lengthBox.onChange = [this] {
const int n = lengthBox.getSelectedId();
Expand Down Expand Up @@ -1548,10 +1548,18 @@ class MainComponent : public juce::Component, private juce::Timer
}
}

/** Each instrument listens on the channel its KeyStep track sends on: the project's channel when it sets one, else the default
(1-4; in drum mode track 1 plays on the drum channel, 10). */
void applyChannels()
{
const bool drums = haveProject && ksp::model::isDrumMode (project);
for (int t = 0; t < 4; ++t)
engine.setChannel (t, haveProject ? ksp::model::effectiveChannel (project, t, drums) : (t == 0 && drums ? 10 : t + 1));
}

void loadPattern()
{
// in drum mode the KeyStep plays track 1 on the drum channel (Drum output, 10 by default), not on the track's own
engine.setChannel (0, haveProject && ksp::model::isDrumMode (project) ? 10 : 1);
applyChannels();
stopRecording();
haveTake = false;
undoTakeButton.setEnabled (false);
Expand Down Expand Up @@ -2273,6 +2281,7 @@ class MainComponent : public juce::Component, private juce::Timer
beforeMapEdit = before;
noteChanged();
refreshMap();
applyChannels();
if (mapWindow != nullptr)
mapWindow->content().setCanUndo (true);
say ("Track " + juce::String (track + 1) + (channel == 0 ? " MIDI channel: Auto" : " MIDI channel " + juce::String (channel))
Expand All @@ -2286,6 +2295,7 @@ class MainComponent : public juce::Component, private juce::Timer
beforeMapEdit.reset();
noteChanged();
refreshMap();
applyChannels();
if (mapWindow != nullptr)
mapWindow->content().setCanUndo (false);
say ("Map edit undone.");
Expand Down
3 changes: 3 additions & 0 deletions Source/Gen/Ambient.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <string>
#include <vector>
#include "Roles.h"
#include "../Model/ArpSettings.h"
#include "../Model/Scene.h"

namespace ksp::gen
Expand Down Expand Up @@ -78,6 +79,8 @@ inline std::string apply (ProjectData& project, const Params& p, int firstTrack
return "variations must be 1-16";
if (firstTrack <= 1 && lastTrack >= 1)
model::setDrumMode (project, false); // track 1 plays notes here: a project left in drum mode by a style would hide them
for (int t = std::max (2, firstTrack); t <= lastTrack; ++t)
model::setArpOn (project, t, false); // a track left on its arpeggiator would arpeggiate the patterns instead of playing them
for (int t = firstTrack; t <= lastTrack; ++t)
for (int v = 0; v < p.variations; ++v)
{
Expand Down
1 change: 1 addition & 0 deletions Source/Gen/Arrangement.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ inline std::string applyArrangement (ProjectData& project, const Style& s, const
return "track 1 pattern " + std::to_string (level + 1) + ": " + e;
for (int t = 2; t <= 4; ++t)
{
model::setArpOn (project, t, false); // the patterns play on the sequencer
const auto& ts = s.tracks[t - 2];
for (int level = 0; level < kLevels; ++level)
for (int chord = 0; chord < kChords; ++chord)
Expand Down
2 changes: 2 additions & 0 deletions Source/Gen/Styles.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ inline std::string applyStyle (ProjectData& project, const Style& s, const Param
if (t == 1)
if (auto e = clearTrack1Sequencer (project, p); ! e.empty())
return e;
if (t >= 2)
model::setArpOn (project, t, false); // the style's patterns play on the sequencer
for (int v = 0; v < p.variations; ++v)
{
std::string e;
Expand Down
12 changes: 12 additions & 0 deletions Source/Model/ArpSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ inline ArpSettings readArp (ProjectData& p, int track, int pat)
return a;
}

/** Turns the arpeggiator of track 2..4 on or off (off = the track plays its sequencer patterns). False when the project lacks the value. */
inline bool setArpOn (ProjectData& p, int track, bool on)
{
if (track < 2 || track > 4)
return false;
int* m = cell (p, item::track (track), ap::mode, { 1 });
if (m == nullptr || *m == kUnreadable)
return false;
*m = on ? (*m | ap::kArpOnBit) : (*m & ~ap::kArpOnBit);
return true;
}

/** Writes mode and octave of the pattern and the swing, division and arp on/off of the track. Returns false when the project lacks those values. */
inline bool writeArp (ProjectData& p, int track, int pat, const ArpSettings& a)
{
Expand Down
10 changes: 10 additions & 0 deletions Source/Model/ProjectSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ inline bool writeSettings (ProjectData& p, double tempo, int swing)
return true;
}

/** The MIDI channel (1..16) the KeyStep plays track 0..3 on: the project's override when set, else the default global channel
(track + 1). Track 1 in drum mode plays on the drum channel (10; a per-project drum channel is not decoded yet). */
inline int effectiveChannel (ProjectData& p, int track, bool drumMode)
{
if (track == 0 && drumMode)
return 10;
const int* c = cell (p, item::project, prs::channel, { static_cast<std::uint8_t> (track + 1) });
return c != nullptr && *c != kUnreadable && *c >= 1 && *c <= 16 ? *c : track + 1;
}

/** Sets the MIDI channel override of a track (track 0..3; channel 0 = global, 1..16). Item 0x78 param 123 [track 1..4]: seen on the device
as 7 for a track set to channel 7, 1 for channel 1. Returns false when the project lacks the value. */
inline bool writeChannel (ProjectData& p, int track, int channel)
Expand Down
5 changes: 3 additions & 2 deletions docs/manual/04-studio-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ Icon: the white chassis, black display strip and the four track colours, drawn b
**mood** (Ambient: Classic, Ethereal, Dark, Meditative; House: Deep, Chill, Melodic; one each for Techno and Drum & bass), a **seed** (New seed rolls a
number; the same seed gives the same music), whether the scenes **chain** the variations and whether the time divisions are **slow**, and
**Generate into this project**: patterns 1-4 of the four tracks and scenes 1-8 of the project that is open are replaced (drums, the arpeggiator
and other patterns are left alone). A style instead puts track 1 in drum mode, writes drum patterns 1-4 there (default drum map, GM notes),
settings and other patterns are left alone; tracks 2-4 are switched to the sequencer, `setArpOn (t, false)`, and the scenes written lose their mutes). A style instead puts track 1 in drum mode, writes drum patterns 1-4 there (default drum map, GM notes),
patterns 1-4 of tracks 2-4 (bass, chords, lead, with their division and Mono/Poly) and scenes 1-4; and the project's tempo (techno 130, house 118-128, dnb 174 BPM). While
track 1 is in drum mode its instrument listens on channel 10 (the KeyStep's default Drum output). Then play it through the instruments with the KeyStep, hear it, edit it, **Save** it to the library and load it into
track 1 is in drum mode its instrument listens on channel 10 (the KeyStep's default Drum output); the other instruments listen on the project's
channel of the track when it sets one (`effectiveChannel`: item 0x78 p123, else 1-4). Then play it through the instruments with the KeyStep, hear it, edit it, **Save** it to the library and load it into
a slot with **Write to slot**. **Undo** brings the project back as it was before the last generation. The settings used are saved with the project
(the library file keeps the style, the mood and the seed). It needs a project open (a library project, a backup or one read from the KeyStep).
3 changes: 2 additions & 1 deletion docs/manual/user/06-instruments.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ others are dimmed. Click anywhere on a card (its controls too) to select its tra
- **M**, which mutes (lights red), and **S**, which solos (lights blue): while any track is soloed only the soloed tracks sound (you can solo
several).

Notes arrive on the track's MIDI channel (T1 = 1 ... T4 = 4, the KeyStep defaults). **Audio...** picks the output
Notes arrive on the track's MIDI channel (T1 = 1 ... T4 = 4, the KeyStep defaults; drums on 10). When the project sets its own
channel for a track (**Project Map**, the T1-T4 channel boxes), the instrument listens on that one. **Audio...** picks the output
device; **Test note** plays a C3 on every loaded instrument without the KeyStep.

**FX...** opens four insert slots per track and a master chain of four; each has **Edit** and **Byp** (bypass). A master
Expand Down
8 changes: 6 additions & 2 deletions docs/manual/user/07-generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
6. Press **Generate into this project**.

**Ambient** replaces patterns 1-4 of the four tracks and scenes 1-8 of the open project, and puts track 1 back in
sequencer mode (a style had put it in drum mode). The arpeggiator, the drum patterns and the other patterns are left alone.
sequencer mode (a style had put it in drum mode). Tracks 2-4 are switched to **Seq** too: a track left on **Arp** would
arpeggiate the generated notes instead of playing them. The drum patterns, the arpeggiator settings and the other patterns are left alone.

**Techno, House and Drum & bass** put track 1 in drum mode and write its drum patterns 1-4 (the last one ends with a
fill), plus patterns 1-4 of track 2 (bass), track 3 (chords) and track 4 (lead), and scenes 1-4. The drums use the
KeyStep's default drum map (kick 36, snare 38, clap 39, closed hat 42, open hat 46), so load a General MIDI style kit on
track 1; the app sends track 1 to its instrument on the drum channel (10). The project also gets the style's tempo
track 1; the app sends track 1 to its instrument on the drum channel (10). Tracks 2-4 are switched to **Seq** (not **Arp**). The project also gets the style's tempo
(techno 130; house deep 128, chill 118, melodic 124; drum & bass 174 BPM); change it on the KeyStep if you like. Track 1 then shows the drum grid (see *The grid*).

**Musical arrangement** (Techno, House, Drum & bass; on by default) writes eight scenes with an energy curve instead of the
Expand All @@ -34,6 +35,9 @@ and pattern 16 of every track is empty, to silence a track. Each scene chains fo
between levels, and all four tracks play the same chord in a scene. The KeyStep does not change scenes by itself: you play
them live, so think of the eight scenes as a map. The Mood and seed still change the notes.

Every generator clears the track mutes of the scenes it writes, so no generated track starts silenced by a mute left from
the project's old scenes. Mute tracks again in the **Project Map** if you want.

The window and the message after generating say which track plays what, so you know which instrument to load on each: styles
put the **drums on track 1**, then bass (track 2), chords (track 3) and a lead melody or bells (track 4); Ambient puts a drone on
track 1, pad chords on 2, a lead melody on 3 and bells on 4.
Expand Down
33 changes: 33 additions & 0 deletions tests/gen_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,39 @@ int main()
CHECK (10, notes > 0 && readPattern (pr, 2, 1).root == 7 && readPattern (pr, 2, 1).scale == 1);
}

// 11. Generated tracks 2-4 play on the sequencer: a track left on its arpeggiator goes back to Seq (its arp settings stay),
// and the scenes written lose their mutes
{
auto armed = [] (ProjectData& pr) {
for (int t = 2; t <= 4; ++t)
{
setArpOn (pr, t, true);
*cell (pr, item::track (t), ap::config, { 1 }) = 20; // Exclu, octave 0
}
*cell (pr, item::scene, sp::mute, { 1 }) = 6;
};
auto seq = [] (ProjectData& pr) {
bool ok = true;
for (int t = 2; t <= 4; ++t)
ok = ok && ! readArp (pr, t, 1).on && readArp (pr, t, 1).mode == 2;
return ok;
};
const auto& house = *gen::findStyle ("house");
auto a1 = blank();
armed (a1);
CHECK (11, readArp (a1, 3, 1).on);
CHECK (11, gen::apply (a1, params).empty() && gen::applyScenes (a1, params).empty() && seq (a1) && readScene (a1, 1).muteRaw == 0);
auto a2 = blank();
armed (a2);
CHECK (11, gen::applyStyle (a2, house, gen::styleParams (house, 4)).empty() && seq (a2));
auto a3 = blank();
armed (a3);
CHECK (11, gen::applyArrangement (a3, house, gen::styleParams (house, 4)).empty() && seq (a3) && readScene (a3, 1).muteRaw == 0);
auto a4 = blank(); // only track 1: the other tracks keep their arpeggiator
armed (a4);
CHECK (11, gen::applyStyle (a4, house, gen::styleParams (house, 4), 1, 1).empty() && readArp (a4, 2, 1).on);
}

std::printf ("track 3 pattern 1: %s\n", gen::preview (a).c_str());
std::printf (failures == 0 ? "gen_test: OK\n" : "gen_test: %d FAIL\n", failures);
return failures == 0 ? 0 : 1;
Expand Down
3 changes: 3 additions & 0 deletions tests/projectmap_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ int main()
CHECK (10, writeChannel (q, 3, 12) && readSettings (q).channel[3] == 12);
CHECK (10, writeChannel (q, 0, 99) && readSettings (q).channel[0] == 16);
CHECK (10, ! writeChannel (q, 4, 1));
// the channel an instrument listens on: the override, else the default (track + 1); drums on 10
CHECK (10, effectiveChannel (q, 0, false) == 16 && effectiveChannel (q, 0, true) == 10 && effectiveChannel (q, 2, false) == 7);
CHECK (10, writeChannel (q, 3, 0) && effectiveChannel (q, 3, false) == 4);
}

std::printf (failures == 0 ? "projectmap_test OK\n" : "projectmap_test FAILED\n");
Expand Down
Loading