use crate::{
     music::{Accidental, Root, RootNote, Scale},
-    Message,
+    Message, MidiInfo, SongInfo,
 };
 
+#[derive(Default, Copy, Clone, Debug)]
 pub struct ControlMessage {
-    probability: f32,
-    randomness: f32,
-    velocity_min: u8,
-    velocity_max: u8,
-    channel: u8,
-    bpm: usize,
-    is_looping: bool,
-    loop_len: usize,
-    step_num: usize,
-    octave: u8,
-    range: u8,
-    scale: Scale,
-    root: Root,
-    voices: u8,
+    pub randomness: f32,
+    pub info: MidiInfo,
+    pub song: SongInfo,
 }
 
 pub fn top_controls<'a>(is_playing: bool) -> Element<'a, Message> {
     .spacing(10);
 
     row![play_button, other_controls]
+        .width(Length::Fill)
         .padding(10)
         .spacing(40)
         .into()
 }
 
 pub fn bottom_controls<'a>(message: ControlMessage) -> Element<'a, Message> {
-    column![
-        map_section(&message),
-        midi_section(&message),
-        song_section(&message)
-    ]
-    .width(Length::Fill)
-    .height(Length::Fill)
-    .padding(10)
-    .spacing(40)
-    .align_items(Alignment::Center)
-    .into()
+    column![map_section(&message), midi_section(&message),]
+        .width(Length::Fill)
+        .height(Length::Fill)
+        .padding(10)
+        .spacing(20)
+        .align_items(Alignment::Center)
+        .into()
 }
 
 fn map_section<'a>(message: &ControlMessage) -> Row<'a, Message> {
     row![
         map_buttons(),
         column![
-            probability_section(message.probability),
+            probability_section(message.info.probability),
             randomize_section(message.randomness)
         ]
         .padding(10)
         .spacing(10)
         .align_items(Alignment::Center)
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
 fn probability_section<'a>(p: f32) -> Row<'a, Message> {
         text("probability a cell gets triggered")
             .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40)))
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
 fn randomize_section<'a>(r: f32) -> Row<'a, Message> {
         text("percent of board to fill on randomize")
             .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40)))
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
-fn map_buttons<'a>() -> Row<'a, Message> {
-    row![
-        button("save")
-            .on_press(Message::Save)
-            .style(theme::Button::Positive),
-        button("clear")
-            .on_press(Message::Clear)
-            .style(theme::Button::Destructive),
-        button("reset")
-            .on_press(Message::Reset)
-            .style(theme::Button::Secondary),
-        button("random")
-            .on_press(Message::Randomize)
-            .style(theme::Button::Primary),
+fn map_buttons<'a>() -> Column<'a, Message> {
+    column![
+        row![
+            button("save")
+                .on_press(Message::Save)
+                .style(theme::Button::Positive),
+            button("clear")
+                .on_press(Message::Clear)
+                .style(theme::Button::Destructive),
+        ]
+        .spacing(10),
+        row![
+            button("reset")
+                .on_press(Message::Reset)
+                .style(theme::Button::Secondary),
+            button("random")
+                .on_press(Message::Randomize)
+                .style(theme::Button::Primary),
+        ]
+        .spacing(10)
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
 fn velocity_sliders<'a>(min: u8, max: u8) -> Column<'a, Message> {
     column![
         row![
-            slider(0..=127, min, Message::NewVMin),
-            text(format!("{min}")),
-            text("minimum velocity")
+            text(format!("{max}")),
+            slider(0..=127, max, Message::NewVMax),
+            text("maximum velocity")
                 .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40)))
         ]
-        .padding(10)
-        .spacing(10)
-        .align_items(Alignment::Center),
+        .spacing(10),
         row![
-            slider(0..=127, max, Message::NewVMax),
-            text(format!("{max}")),
-            text("maximum velocity")
+            text(format!("{min}")),
+            slider(0..=127, min, Message::NewVMin),
+            text("minimum velocity")
                 .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40)))
         ]
-        .padding(10)
-        .spacing(10)
-        .align_items(Alignment::Center)
+        .spacing(10),
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
-fn midi_section<'a>(message: &ControlMessage) -> Row<'a, Message> {
-    row![
-        channel_selector(message.channel),
-        velocity_sliders(message.velocity_min, message.velocity_max)
-    ]
-    .padding(10)
-    .spacing(10)
-    .align_items(Alignment::Center)
-}
-
-fn channel_selector<'a>(channel: u8) -> Row<'a, Message> {
-    row![
-        button("-").on_press(Message::ChannelChange(channel.saturating_sub(1))),
-        text(format!("channel: {channel}")),
-        button("+").on_press(Message::ChannelChange(channel.saturating_add(1))),
+fn midi_section<'a>(message: &ControlMessage) -> Column<'a, Message> {
+    column![
+        song_section(message),
+        velocity_sliders(message.info.velocity.min(), message.info.velocity.max())
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
 fn song_section<'a>(message: &ControlMessage) -> Row<'a, Message> {
     row![
         column![
-            loop_controls(message.is_looping, message.loop_len, message.step_num),
-            speed_controls(message.bpm)
+            loop_controls(
+                message.song.is_looping,
+                message.song.loop_len,
+                message.song.step_num
+            ),
+            speed_controls(message.song.bpm),
+            division_controls(message.song.divisor),
         ]
         .padding(10)
         .spacing(10)
         .align_items(Alignment::Center),
         column![
-            voice_controls(message.voices),
-            octave_selector(message.octave, message.range),
+            voice_controls(message.info.voices, message.info.channel),
+            octave_selector(message.info.octave.center(), message.info.octave.range()),
             scale_selector(
-                message.scale,
-                message.root.get_note(),
-                message.root.get_accidental()
+                message.info.scale,
+                message.info.root.get_note(),
+                message.info.root.get_accidental()
             )
         ]
         .padding(10)
         .spacing(10)
         .align_items(Alignment::Center)
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
 fn octave_selector<'a>(oct: u8, range: u8) -> Row<'a, Message> {
     row![
-        button("-")
-            .on_press(Message::NewOctave(oct.saturating_sub(1)))
-            .style(theme::Button::Destructive),
+        button("-").on_press(Message::NewOctave(oct.saturating_sub(1))),
         text(format!("octave: {oct}")),
-        button("+")
-            .on_press(Message::NewOctave(oct.saturating_add(1)))
-            .style(theme::Button::Positive),
-        button("-")
-            .on_press(Message::OctaveRange(range.saturating_sub(1)))
-            .style(theme::Button::Destructive),
+        button("+").on_press(Message::NewOctave(oct.saturating_add(1))),
+        button("-").on_press(Message::OctaveRange(range.saturating_sub(1))),
         text(format!("range: +/-{range}")),
-        button("+")
-            .on_press(Message::OctaveRange(range.saturating_add(1)))
-            .style(theme::Button::Positive),
+        button("+").on_press(Message::OctaveRange(range.saturating_add(1)))
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
 fn loop_controls<'a>(looping: bool, len: usize, step: usize) -> Row<'a, Message> {
         }),
         button("+").on_press(Message::LoopLength(len.saturating_add(1)))
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
 fn speed_controls<'a>(bpm: usize) -> Row<'a, Message> {
         button(">").on_press(Message::SpeedChanged(bpm.saturating_add(1))),
         button(">>").on_press(Message::SpeedChanged(bpm.saturating_add(5))),
     ]
-    .width(Length::Fill)
-    .align_items(Alignment::Center)
     .spacing(10)
 }
 
-fn voice_controls<'a>(voices: u8) -> Row<'a, Message> {
+fn division_controls<'a>(divisor: usize) -> Row<'a, Message> {
     row![
-        button("-")
-            .on_press(Message::Voices(voices.saturating_sub(1)))
-            .style(theme::Button::Destructive),
+        button("-").on_press(if divisor > 1 {
+            Message::NewDivision(divisor.saturating_sub(1))
+        } else {
+            Message::None
+        }),
+        text(format!("note division: {divisor}")),
+        button("+").on_press(Message::NewDivision(divisor.saturating_add(1)))
+    ]
+    .spacing(10)
+}
+
+fn voice_controls<'a>(voices: u8, channel: u8) -> Row<'a, Message> {
+    row![
+        button("-").on_press(Message::ChannelChange(channel.saturating_sub(1))),
+        text(format!("channel: {channel}")),
+        button("+").on_press(Message::ChannelChange(channel.saturating_add(1))),
+        button("-").on_press(Message::Voices(voices.saturating_sub(1))),
         text(format!("voices: {voices}")),
-        button("+")
-            .on_press(Message::Voices(voices.saturating_add(1)))
-            .style(theme::Button::Positive),
+        button("+").on_press(Message::Voices(voices.saturating_add(1))),
     ]
-    .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center)
 }
 
 fn scale_selector<'a>(scale: Scale, note: RootNote, acc: Accidental) -> Row<'a, Message> {
     row![
-        pick_list(&Scale::ALL[..], Some(scale), Message::Scale),
         pick_list(&RootNote::ALL[..], Some(note), move |note| {
             Message::NewNote(Root::new(note, acc))
         }),
         pick_list(&Accidental::ALL[..], Some(acc), move |acc| {
             Message::NewNote(Root::new(note, acc))
-        })
+        }),
+        pick_list(&Scale::ALL[..], Some(scale), Message::Scale),
     ]
-    .width(Length::Fill)
     .align_items(Alignment::Center)
-    .spacing(10)
 }
 
     map: Map,
     mask: Mask,
     midi: MidiLink,
+    song: SongInfo,
     info: MidiInfo,
-    is_playing: bool,
-    bpm: usize,
-    is_looping: bool,
-    loop_len: usize,
-    step_num: usize,
-    randomness: f32,
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct SongInfo {
+    pub is_playing: bool,
+    pub bpm: usize,
+    pub divisor: usize,
+    pub is_looping: bool,
+    pub loop_len: usize,
+    pub step_num: usize,
+}
+
+impl Default for SongInfo {
+    fn default() -> Self {
+        Self {
+            is_playing: false,
+            bpm: 120,
+            divisor: 1,
+            is_looping: false,
+            loop_len: 16,
+            step_num: 0,
+        }
+    }
 }
 
 #[derive(Debug, Clone)]
     Save,
     TogglePlayback,
     SpeedChanged(usize),
+    NewDivision(usize),
     ToggleLoop,
     LoopLength(usize),
     ProbChanged(f32),
 
 impl CellSeq {
     fn control_message(&self) -> ControlMessage {
-        todo!()
+        ControlMessage {
+            randomness: self.map.randomness(),
+            info: self.info.clone(),
+            song: self.song.clone(),
+        }
     }
 }
 
     fn new(flags: Self::Flags) -> (Self, Command<Message>) {
         (
             Self {
-                bpm: 120,
-                loop_len: 16,
                 midi: flags,
                 ..Self::default()
             },
 
     fn update(&mut self, message: Message) -> Command<Message> {
         match message {
+            Message::Quit => todo!(), // TODO: figure out how to cleanly quit
             Message::None => {}
+            Message::MapMessage(message) => self.map.update(message),
+            Message::MaskMessage(message) => self.mask.update(message),
+            Message::HitCount(x) => self.midi.update(x, &self.info),
             Message::NewMap(m) => {
                 self.map.update(map::Message::Ticked(m.clone()));
                 let hits = self.mask.tick(m);
                 return Command::perform(async move { hits }, Message::HitCount);
             }
-            Message::HitCount(x) => self.midi.update(x, &self.info),
-            Message::MapMessage(message) => self.map.update(message),
-            Message::MaskMessage(message) => self.mask.update(message),
             Message::Tick(_) => {
-                let map = if self.is_looping && self.step_num > self.loop_len {
-                    self.step_num = 0;
+                let map = if self.song.is_looping && self.song.step_num > self.song.loop_len {
+                    self.song.step_num = 0;
                     self.map.reset_loop()
                 } else {
-                    self.step_num += 1;
+                    self.song.step_num += 1;
                     self.map.tick()
                 };
 
                 return Command::batch(commands);
             }
             Message::TogglePlayback => {
-                self.is_playing = !self.is_playing;
-                if self.is_playing {
+                self.song.is_playing = !self.song.is_playing;
+                if self.song.is_playing {
                     self.map.save()
                 }
             }
-            Message::SpeedChanged(bpm) => self.bpm = bpm,
-            Message::Clear => self.map.clear(),
-            Message::Randomize => self.map.randomize(self.randomness),
-            Message::Reset => self.map.reset(),
-            Message::Save => self.map.save(),
             Message::ToggleLoop => {
-                self.is_looping = !self.is_looping;
-                if self.is_looping {
-                    self.step_num = 0;
+                self.song.is_looping = !self.song.is_looping;
+                if self.song.is_looping {
+                    self.song.step_num = 0;
                 }
             }
-            Message::LoopLength(len) => self.loop_len = len,
-            Message::Quit => todo!(),
+            Message::Clear => self.map.clear(),
+            Message::Randomize => self.map.randomize(),
+            Message::Reset => self.map.reset(),
+            Message::Save => self.map.save(),
+            Message::SpeedChanged(b) => self.song.bpm = b,
+            Message::NewDivision(d) => self.song.divisor = d,
+            Message::LoopLength(l) => self.song.loop_len = l,
             Message::ProbChanged(p) => self.info.probability = p,
-            Message::RandChanged(r) => self.randomness = r,
+            Message::RandChanged(r) => self.map.set_randomness(r),
             Message::NewVMin(v) => self.info.velocity.set_min(v),
             Message::NewVMax(v) => self.info.velocity.set_max(v),
             Message::ChannelChange(c) => self.info.channel = c,
     }
 
     fn subscription(&self) -> Subscription<Message> {
-        if self.is_playing {
-            time::every(Duration::from_millis(60000 / self.bpm as u64)).map(Message::Tick)
+        if self.song.is_playing {
+            time::every(Duration::from_millis(
+                60000 / (self.song.bpm * self.song.divisor) as u64,
+            ))
+            .map(Message::Tick)
         } else {
             Subscription::none()
         }
     }
 
     fn view(&self) -> Element<Message> {
-        let top = top_controls(self.is_playing);
+        let top = top_controls(self.song.is_playing);
 
         let map = row![
             self.map.view().map(Message::MapMessage),