use enum_ordinalize::Ordinalize;
use serde::{Deserialize, Serialize};
use crate::fp_grids_lib::resources::{DRUM_MAP, K_NUM_PARTS, K_NUM_STEPS_PER_PATTERN};
use crate::fp_grids_lib::utils::{u8_mix, u8_u8_mul_shift8, Random};
use crate::utils::euclidean_pattern;
const K_PULSE_DURATION: u8 = 8;
#[derive(Debug, Clone, Copy)]
pub enum PatternModeSettings {
Drums { x: u8, y: u8, randomness: u8 },
Euclidean { chaos_amount: u8 },
DnB { pattern: u8 },
}
#[derive(Debug, Clone, Copy)]
pub struct PatternGeneratorSettings {
pub options: PatternModeSettings,
pub density: [u8; K_NUM_PARTS],
}
#[derive(Debug, Clone, Copy, PartialEq, Ordinalize)]
pub enum OutputMode {
OutputModeEuclidean,
OutputModeDrums,
OutputModeDnB,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Options {
pub output_mode: OutputMode,
pub gate_mode: bool, }
#[allow(dead_code)]
pub enum OutputBits {
OutputBitTrig1,
OutputBitTrig2,
OutputBitTrig3,
OutputBitAccent,
OutputBitClock, OutputBitReset, }
impl OutputBits {
pub fn to_bitmask(&self) -> u8 {
match self {
OutputBits::OutputBitTrig1 => 1,
OutputBits::OutputBitTrig2 => 2,
OutputBits::OutputBitTrig3 => 4,
OutputBits::OutputBitAccent => 8,
OutputBits::OutputBitClock => 16,
OutputBits::OutputBitReset => 32,
}
}
}
pub const DNB_MAX_STEPS: usize = 32;
pub const DNB_NUM_PATTERNS: u8 = 12;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct DnbDrumPattern {
pub kick: [bool; DNB_MAX_STEPS],
pub snare: [bool; DNB_MAX_STEPS],
pub hihat: [bool; DNB_MAX_STEPS],
pub has_ghost: bool,
pub ghost_snare: [bool; DNB_MAX_STEPS],
pub steps: u8, }
impl Default for DnbDrumPattern {
fn default() -> Self {
Self {
kick: [false; DNB_MAX_STEPS],
snare: [false; DNB_MAX_STEPS],
hihat: [false; DNB_MAX_STEPS],
has_ghost: false,
ghost_snare: [false; DNB_MAX_STEPS],
steps: 16,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct SequencerState {
pub sequence_step: u8,
pub euclidean_step: [u8; K_NUM_PARTS],
pub pulse: u8,
pub pulse_duration_counter: u16,
pub current_dnb_pattern: DnbDrumPattern,
pub base_dnb_pattern: DnbDrumPattern,
}
impl Default for SequencerState {
fn default() -> Self {
Self {
sequence_step: 0,
euclidean_step: [0; K_NUM_PARTS],
pulse: 0,
pulse_duration_counter: 0,
base_dnb_pattern: DnbDrumPattern::default(),
current_dnb_pattern: DnbDrumPattern::default(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PatternGenerator {
pub settings_: [PatternGeneratorSettings; 3], pub options_: Options,
chaos_globally_enabled_: bool,
current_euclidean_length: [u8; K_NUM_PARTS], fill: [u8; K_NUM_PARTS], part_perturbation: [u8; K_NUM_PARTS], euclidean_step: [u8; K_NUM_PARTS], euclidean_offset: [u8; K_NUM_PARTS],
state_: u8, step_: u8,
sequence_step_: u8, first_beat_: bool, beat_: bool,
pulse_: u8,
pulse_duration_counter_: u16,
random: Random,
current_dnb_pattern: DnbDrumPattern, base_dnb_pattern: DnbDrumPattern, queued_pattern_id: i8, pattern_change_queued: bool, }
impl PatternGenerator {
pub fn new() -> Self {
Self::default()
}
pub fn get_step(&self) -> u8 {
self.step_
}
pub fn get_sequencer_state(&self) -> SequencerState {
SequencerState {
sequence_step: self.sequence_step_,
euclidean_step: self.euclidean_step,
pulse: self.pulse_,
pulse_duration_counter: self.pulse_duration_counter_,
base_dnb_pattern: self.base_dnb_pattern,
current_dnb_pattern: self.current_dnb_pattern,
}
}
pub fn is_gate_mode_active(&self) -> bool {
self.options_.gate_mode
}
pub fn get_current_output_mode(&self) -> OutputMode {
self.options_.output_mode
}
pub fn set_output_mode(&mut self, mode: OutputMode) {
self.options_.output_mode = mode;
}
pub fn set_gate_mode(&mut self, active: bool) {
self.options_.gate_mode = active;
}
pub fn set_global_chaos(&mut self, enabled: bool) {
self.chaos_globally_enabled_ = enabled;
}
pub fn get_trigger_state(&self) -> u8 {
self.state_
}
pub fn is_on_first_beat(&self) -> bool {
self.first_beat_
}
pub fn is_on_beat(&self) -> bool {
self.beat_
}
pub fn set_seed(&mut self, seed: u16) {
self.random.seed(seed);
}
pub fn queue_dnb_pattern_change(&mut self, new_pattern: u8) {
self.queued_pattern_id = new_pattern.clamp(0, DNB_NUM_PATTERNS - 1) as i8;
self.pattern_change_queued = true;
}
pub fn reset_dnb_pattern_to_base(&mut self) {
self.current_dnb_pattern = self.base_dnb_pattern;
}
pub fn get_dnb_24ppqn_pattern_division(&self) -> u32 {
match self.current_dnb_pattern.steps {
16 | 32 => 6, 24 => 4, _ => 6,
}
}
pub fn reset(&mut self) {
self.step_ = 0;
self.sequence_step_ = 0;
self.pulse_ = 0;
self.euclidean_step = [0; K_NUM_PARTS];
self.part_perturbation = [0; K_NUM_PARTS];
self.first_beat_ = true;
self.beat_ = true;
self.state_ = 0;
self.pulse_duration_counter_ = 0;
self.current_dnb_pattern = self.base_dnb_pattern;
self.queued_pattern_id = -1;
self.pattern_change_queued = false;
self.evaluate();
}
pub fn restore(&mut self, sequencer_state: SequencerState) {
self.sequence_step_ = sequencer_state.sequence_step;
self.euclidean_step = sequencer_state.euclidean_step;
self.pulse_ = sequencer_state.pulse;
self.pulse_duration_counter_ = sequencer_state.pulse_duration_counter;
self.step_ = self.sequence_step_;
self.first_beat_ = self.sequence_step_ == 0;
let mut steps_per_beat = K_NUM_STEPS_PER_PATTERN / 4;
if steps_per_beat == 0 {
steps_per_beat = 1;
} self.beat_ = self.sequence_step_.is_multiple_of(steps_per_beat);
self.base_dnb_pattern = sequencer_state.base_dnb_pattern;
self.current_dnb_pattern = sequencer_state.current_dnb_pattern;
self.queued_pattern_id = -1;
self.pattern_change_queued = false;
}
pub fn retrigger(&mut self) {
self.evaluate();
}
pub fn tick(&mut self, clkn: u32, div: u32) {
let step_count = clkn / div;
self.sequence_step_ = if self.options_.output_mode == OutputMode::OutputModeDnB {
(step_count % self.current_dnb_pattern.steps as u32) as u8
} else {
(step_count % K_NUM_STEPS_PER_PATTERN as u32) as u8
};
self.step_ = self.sequence_step_;
for part in 0..K_NUM_PARTS {
self.euclidean_step[part] =
(step_count % self.current_euclidean_length[part] as u32) as u8;
}
self.first_beat_ = self.sequence_step_ == 0;
let mut steps_per_beat = K_NUM_STEPS_PER_PATTERN / 4;
if steps_per_beat == 0 {
steps_per_beat = 1;
}
self.beat_ = self.sequence_step_.is_multiple_of(steps_per_beat);
self.evaluate();
self.increment_pulse_counter();
}
pub fn set_length(&mut self, channel: usize, length: u8) {
if channel > K_NUM_PARTS {
return;
}
let clamped_length = length.clamp(1, 32);
self.current_euclidean_length[channel] = clamped_length;
self.euclidean_offset[channel] %= clamped_length;
}
pub fn set_offset(&mut self, channel: usize, offset: u8) {
if channel > K_NUM_PARTS {
return;
}
let length = self.current_euclidean_length[channel].max(1);
self.euclidean_offset[channel] = offset % length;
}
pub fn set_fill(&mut self, channel: usize, fill_param_value: u8) {
if channel > K_NUM_PARTS {
return;
}
self.settings_[OutputMode::OutputModeEuclidean.ordinal() as usize].density[channel] =
fill_param_value;
let current_length = self.current_euclidean_length[channel];
if current_length == 0 {
self.fill[channel] = 0;
return;
}
let mut active_steps: u8 =
((fill_param_value as u16 * current_length as u16 + 127) / 255) as u8;
if active_steps > current_length {
active_steps = current_length; }
if fill_param_value > 0 && active_steps == 0 && current_length > 0 {
active_steps = 1; }
if fill_param_value == 255 {
active_steps = current_length; }
self.fill[channel] = active_steps;
}
}
impl Default for PatternGenerator {
fn default() -> Self {
Self {
part_perturbation: [0; K_NUM_PARTS],
euclidean_step: [0; K_NUM_PARTS],
options_: Options {
output_mode: OutputMode::OutputModeDrums,
gate_mode: false,
},
chaos_globally_enabled_: false,
state_: 0,
step_: 0, sequence_step_: 0,
first_beat_: true, beat_: true,
pulse_: 0,
pulse_duration_counter_: 0,
settings_: [
PatternGeneratorSettings {
options: PatternModeSettings::Euclidean {
chaos_amount: 0, },
density: [128, 128, 128], },
PatternGeneratorSettings {
options: PatternModeSettings::Drums {
x: 128, y: 128, randomness: 0, },
density: [255, 255, 255], },
PatternGeneratorSettings {
options: PatternModeSettings::DnB {
pattern: 0, },
density: [255, 255, 255], },
],
current_euclidean_length: [16; K_NUM_PARTS], fill: [8; K_NUM_PARTS], euclidean_offset: [0; K_NUM_PARTS],
random: Random::default(),
base_dnb_pattern: DnbDrumPattern::default(),
current_dnb_pattern: DnbDrumPattern::default(),
queued_pattern_id: -1,
pattern_change_queued: false,
}
}
}
impl PatternGenerator {
fn increment_pulse_counter(&mut self) {
self.pulse_duration_counter_ += 1;
if self.pulse_duration_counter_ >= K_PULSE_DURATION as u16 && !self.options_.gate_mode {
self.state_ = 0;
}
}
fn read_drum_map(&self, step: u8, instrument: u8, x: u8, y: u8) -> u8 {
let i = x >> 6; let j = y >> 6;
let i_idx = i.min(3) as usize;
let j_idx = j.min(3) as usize;
let a_map = DRUM_MAP[j_idx][i_idx]; let b_map = DRUM_MAP[j_idx][i_idx + 1]; let c_map = DRUM_MAP[j_idx + 1][i_idx]; let d_map = DRUM_MAP[j_idx + 1][i_idx + 1];
let offset = ((instrument as usize) * K_NUM_STEPS_PER_PATTERN as usize) + step as usize;
let a = a_map[offset];
let b = b_map[offset];
let c = c_map[offset];
let d = d_map[offset];
let x_weight = (x % 64) << 2;
let y_weight = (y % 64) << 2;
u8_mix(
u8_mix(a, b, x_weight), u8_mix(c, d, x_weight), y_weight, )
}
fn evaluate(&mut self) {
self.pulse_duration_counter_ = 0;
match self.options_.output_mode {
OutputMode::OutputModeDrums => self.evaluate_drums(),
OutputMode::OutputModeEuclidean => self.evaluate_euclidean(),
OutputMode::OutputModeDnB => self.evaluate_dnb(),
}
}
fn evaluate_drums(&mut self) {
if self.step_ == 0 && self.pulse_ == 0 {
if self.chaos_globally_enabled_ {
let mut randomness =
match self.settings_[OutputMode::OutputModeDrums.ordinal() as usize].options {
PatternModeSettings::Drums { randomness, .. } => randomness,
_ => 0, };
randomness >>= 2; for part in 0..K_NUM_PARTS {
self.part_perturbation[part] =
u8_u8_mul_shift8(self.random.get_byte(), randomness);
}
} else {
self.part_perturbation = [0; K_NUM_PARTS];
}
}
let current_step_in_pattern = self.step_;
let mut new_state_for_tick = 0u8;
let (x, y) = match self.settings_[OutputMode::OutputModeDrums.ordinal() as usize].options {
PatternModeSettings::Drums { x, y, .. } => (x, y),
_ => (0, 0), };
let mut density_thresholds = [0u8; K_NUM_PARTS];
for (part, density) in self.settings_[OutputMode::OutputModeDrums.ordinal() as usize]
.density
.iter()
.enumerate()
.take(K_NUM_PARTS)
{
density_thresholds[part] = !*density;
}
let mut accent_bits_for_parts: u8 = 0; for (part, threshold) in density_thresholds.iter().enumerate().take(K_NUM_PARTS) {
let mut level: u8 = self.read_drum_map(current_step_in_pattern, part as u8, x, y);
if level < 255 - self.part_perturbation[part] {
level += self.part_perturbation[part];
} else {
level = 255;
}
if level > *threshold {
if level > 192 {
accent_bits_for_parts |= 1 << part; }
new_state_for_tick |= 1 << part; }
}
if accent_bits_for_parts != 0 {
new_state_for_tick |= OutputBits::OutputBitAccent.to_bitmask();
}
self.state_ = new_state_for_tick; }
fn evaluate_euclidean(&mut self) {
self.state_ = 0;
let chaos = match self.settings_[OutputMode::OutputModeEuclidean.ordinal() as usize].options
{
PatternModeSettings::Euclidean { chaos_amount } => chaos_amount,
_ => 0,
};
for part in 0..K_NUM_PARTS {
let length = self.current_euclidean_length[part].max(2);
let beats = self.settings_[OutputMode::OutputModeEuclidean.ordinal() as usize].density
[part]
.min(length);
let offset = self.euclidean_offset[part];
let pattern_bits = euclidean_pattern(length, beats, offset, 0);
let pos = self.euclidean_step[part] % length;
if (pattern_bits >> pos) & 1 == 1 {
self.state_ |= 1 << part;
}
if self.chaos_globally_enabled_
&& chaos > 0
&& (self.random.get_word() % 256) < chaos as u16
{
if self.random.get_word().is_multiple_of(8) {
self.state_ ^= 1 << part;
}
if chaos > 192 && self.random.get_word().is_multiple_of(16) {
self.state_ |= OutputBits::OutputBitAccent.to_bitmask();
}
}
}
}
fn evaluate_dnb(&mut self) {
if self.first_beat_ && self.pulse_ == 0 {
if self.pattern_change_queued && self.queued_pattern_id >= 0 {
self.generate_dnb_pattern(self.queued_pattern_id as u8);
self.queued_pattern_id = -1;
self.pattern_change_queued = false;
}
}
let current_step_in_pattern = self.step_ as usize;
let mut new_state_for_tick = 0u8; if self.current_dnb_pattern.kick[current_step_in_pattern]
&& self.random.get_byte()
< self.settings_[OutputMode::OutputModeDnB.ordinal() as usize].density[0]
{
new_state_for_tick |= 1 << 0;
}
if self.current_dnb_pattern.snare[current_step_in_pattern]
&& self.random.get_byte()
< self.settings_[OutputMode::OutputModeDnB.ordinal() as usize].density[1]
{
new_state_for_tick |= 1 << 1;
}
if self.current_dnb_pattern.hihat[current_step_in_pattern] {
new_state_for_tick |= 1 << 2;
}
if self.current_dnb_pattern.ghost_snare[current_step_in_pattern]
&& self.random.get_byte()
< self.settings_[OutputMode::OutputModeDnB.ordinal() as usize].density[2]
{
new_state_for_tick |= 1 << 3;
}
self.state_ = new_state_for_tick; }
fn generate_dnb_pattern(&mut self, pattern_id: u8) {
let mut p = DnbDrumPattern::default();
match pattern_id {
0 => {
p.kick = [
true, false, false, false, false, false, false, false, false, false, true,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, false, false, true, false, false, false, false, false, false,
false, true, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
1 => {
p.kick = [
true, false, false, false, false, false, false, false, false, false, true,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, false, false, true, false, false, false, false, false, false,
false, false, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
2 => {
p.has_ghost = true;
p.kick = [
true, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, false, false, true, false, false, false, false, false, true,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.ghost_snare = [
false, false, false, false, false, false, false, true, false, true, false,
false, false, true, false, true,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
3 => {
p.has_ghost = true;
p.kick = [
true, false, true, false, false, false, false, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, true, false, false, true, false, false, true, false, false, true,
false, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, true, true, true, true, true, true, true, true, true, true, true, true,
true, true, true,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.ghost_snare = [
false, false, false, true, true, false, true, true, false, true, true, false,
true, true, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
4 => {
p.has_ghost = true;
p.kick = [
true, false, false, false, false, false, false, false, true, false, false,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, false, false, true, false, false, false, false, false, true,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.ghost_snare = [
false, false, false, false, false, false, false, false, false, false, false,
false, false, true, false, true,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
5 => {
p.kick = [
true, false, false, false, false, false, true, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, false, false, false, false, false, false, false, false, false,
false, true, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
6 => {
p.steps = 32;
p.kick = [
true, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false, true, false,
];
p.snare = [
false, false, false, false, true, false, false, false, true, false, false,
false, true, false, false, false, true, false, false, false, true, false,
false, false, true, false, false, false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false, true, false, true, false,
];
}
7 => {
p.kick = [
true, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, false, false, false, false, false, false, true, false, false,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
8 => {
p.steps = 24;
p.kick = [
true, false, false, false, false, false, true, false, false, false, false,
false, true, false, false, false, false, false, true, false, false, false,
false, false,
false, false, false, false, false, false, false, false,
];
p.snare = [
false, false, false, false, false, false, true, false, false, false, false,
false, false, false, false, false, false, false, true, false, false, false,
false, false,
false, false, false, false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false, true, false, true, false, true, false, true,
false,
false, false, false, false, false, false, false, false,
];
}
9 => {
p.has_ghost = true;
p.kick = [
true, false, false, false, false, false, false, false, false, false, true,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, false, false, true, false, false, true, false, true, false,
false, true, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.ghost_snare = [
false, false, false, false, false, false, true, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
10 => {
p.has_ghost = true;
p.kick = [
true, false, false, false, false, true, false, false, true, false, false,
false, false, true, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, false, false, true, false, false, false, false, false, false,
false, true, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, false, true, true, true, false, true, false, true, false, true, true,
true, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.ghost_snare = [
false, false, false, true, false, false, false, false, false, false, false,
true, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
11 => {
p.kick = [
true, false, false, true, false, false, true, false, true, false, false, true,
false, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.snare = [
false, false, false, false, false, false, false, false, true, false, false,
false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
p.hihat = [
true, false, true, false, true, false, true, false, true, false, true, false,
true, false, true, false,
false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false,
];
}
_ => {}
}
self.base_dnb_pattern = p;
self.current_dnb_pattern = p;
}
pub fn generate_dnb_variation(&mut self) {
let mut track = self.random.get_byte() % 3; if track >= 2 {
track = 3; }
let (base_track, matched) = match track {
0 => {
(self.base_dnb_pattern.kick, true)
}
1 => {
(self.base_dnb_pattern.snare, true)
}
3 if self.current_dnb_pattern.has_ghost => {
(self.base_dnb_pattern.ghost_snare, true)
}
_ => ([false; DNB_MAX_STEPS], false),
};
if matched {
let mut attempts = 0;
let mut position = 0usize;
let mut found_hit = false;
while attempts < self.base_dnb_pattern.steps && !found_hit {
position = (self.random.get_byte() % self.base_dnb_pattern.steps)
.clamp(0, DNB_MAX_STEPS as u8 - 1) as usize;
if base_track[position] {
found_hit = true;
}
attempts += 1;
}
if found_hit {
match track {
0 => {
self.current_dnb_pattern.kick[position] =
!self.current_dnb_pattern.kick[position];
}
1 => {
self.current_dnb_pattern.snare[position] =
!self.current_dnb_pattern.snare[position];
}
3 => {
self.current_dnb_pattern.ghost_snare[position] =
!self.current_dnb_pattern.ghost_snare[position];
}
_ => {}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use env_logger::Env;
fn init_logger() {
let _ = env_logger::Builder::from_env(Env::default().default_filter_or("warn"))
.is_test(true)
.try_init();
}
#[test]
fn test_initialization() {
let mut generator = PatternGenerator::default();
generator.reset(); assert_eq!(0, generator.step_);
assert_eq!(0, generator.sequence_step_);
assert_eq!(0, generator.pulse_);
assert_eq!(true, generator.first_beat_);
assert_eq!(true, generator.beat_);
assert_eq!(15, generator.state_);
assert_eq!(0, generator.sequence_step_);
assert_eq!(16, generator.base_dnb_pattern.steps);
assert_eq!(16, generator.current_dnb_pattern.steps);
}
#[test]
fn test_drum_map_interpolation() {
let generator: PatternGenerator = PatternGenerator::default();
let value = generator.read_drum_map(0, 0, 32, 32);
assert_eq!(value, 217);
}
#[test]
fn test_evaluate_drums() {
init_logger();
let mut generator: PatternGenerator = PatternGenerator::default();
generator.set_seed(0xFFF1);
generator.options_.output_mode = OutputMode::OutputModeDrums;
generator.options_.gate_mode = true;
generator.settings_[OutputMode::OutputModeDrums.ordinal() as usize].options =
PatternModeSettings::Drums {
x: 0,
y: 0,
randomness: 0,
};
generator.settings_[OutputMode::OutputModeDrums.ordinal() as usize].density =
[31; K_NUM_PARTS];
generator.evaluate();
assert_eq!(0, generator.get_step());
assert_eq!(12, generator.get_trigger_state());
generator.tick(1, 1);
assert_eq!(1, generator.get_step());
assert_eq!(0, generator.get_trigger_state());
generator.tick(2, 1);
assert_eq!(2, generator.get_step());
assert_eq!(0, generator.get_trigger_state());
generator.tick(3, 1);
assert_eq!(3, generator.get_step());
assert_eq!(0, generator.get_trigger_state());
generator.tick(4, 1);
assert_eq!(4, generator.get_step());
assert_eq!(0, generator.get_trigger_state());
}
#[test]
fn test_evaluate_euclidean() {
init_logger();
let mut generator: PatternGenerator = PatternGenerator::default();
generator.options_.output_mode = OutputMode::OutputModeEuclidean;
generator.options_.gate_mode = true;
generator.settings_[OutputMode::OutputModeEuclidean.ordinal() as usize].options =
PatternModeSettings::Euclidean { chaos_amount: 0 };
for part in 0..K_NUM_PARTS {
generator.set_length(part, 8);
}
generator.settings_[OutputMode::OutputModeEuclidean.ordinal() as usize].density =
[3; K_NUM_PARTS];
generator.evaluate();
assert_eq!(0, generator.get_step());
assert_eq!(7, generator.get_trigger_state());
generator.tick(1, 1);
assert_eq!(1, generator.get_step());
assert_eq!(0, generator.get_trigger_state());
generator.tick(2, 1);
assert_eq!(2, generator.get_step());
assert_eq!(0, generator.get_trigger_state());
generator.tick(3, 1);
assert_eq!(3, generator.get_step());
assert_eq!(7, generator.get_trigger_state());
generator.tick(4, 1);
assert_eq!(4, generator.get_step());
assert_eq!(0, generator.get_trigger_state());
generator.tick(6, 1);
assert_eq!(6, generator.get_step());
assert_eq!(7, generator.get_trigger_state());
generator.tick(7, 1);
assert_eq!(7, generator.get_step());
assert_eq!(0, generator.get_trigger_state()); }
#[test]
fn test_evaluate_dnb() {
init_logger();
let mut generator: PatternGenerator = PatternGenerator::default();
generator.set_seed(0xFFF1);
generator.options_.output_mode = OutputMode::OutputModeDnB;
generator.settings_[OutputMode::OutputModeDnB.ordinal() as usize].options =
PatternModeSettings::DnB { pattern: 0 };
generator.settings_[OutputMode::OutputModeDrums.ordinal() as usize].density =
[255; K_NUM_PARTS];
generator.queue_dnb_pattern_change(0);
generator.evaluate();
assert_eq!(0, generator.get_step());
assert_eq!(5 , generator.get_trigger_state());
generator.tick(1, 1);
assert_eq!(1, generator.get_step());
assert_eq!(0, generator.get_trigger_state());
generator.queue_dnb_pattern_change(2);
generator.reset();
generator.evaluate();
assert_eq!(0, generator.get_step());
assert_eq!(5 , generator.get_trigger_state());
}
#[test]
fn test_tick_with_division() {
let mut generator: PatternGenerator = PatternGenerator::default();
generator.set_seed(0xFFF1);
generator.options_.output_mode = OutputMode::OutputModeEuclidean;
generator.tick(3, 3);
assert_eq!(1, generator.get_step());
generator.tick(6, 3);
assert_eq!(2, generator.get_step());
generator.tick(9, 3);
assert_eq!(3, generator.get_step());
generator.tick(0, 3);
assert_eq!(0, generator.get_step());
generator.tick(15, 3);
assert_eq!(
5 % K_NUM_STEPS_PER_PATTERN as u32,
generator.get_step() as u32
);
}
}