diff --git a/.idea/misc.xml b/.idea/misc.xml
index e3b7c48..5c9d3cd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -12,7 +12,7 @@
         <entry key="app/src/main/res/layout/fragment_instrument.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_instrument_list.xml" value="0.2805755395683453" />
         <entry key="app/src/main/res/layout/fragment_main.xml" value="0.1" />
-        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.24644549763033174" />
+        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_song.xml" value="0.24644549763033174" />
         <entry key="app/src/main/res/layout/sample_credits_tab.xml" value="0.25" />
         <entry key="app/src/main/res/layout/sample_instrument_view.xml" value="0.33" />

diff --git a/.idea/misc.xml b/.idea/misc.xml
index e3b7c48..5c9d3cd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -12,7 +12,7 @@
         <entry key="app/src/main/res/layout/fragment_instrument.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_instrument_list.xml" value="0.2805755395683453" />
         <entry key="app/src/main/res/layout/fragment_main.xml" value="0.1" />
-        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.24644549763033174" />
+        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_song.xml" value="0.24644549763033174" />
         <entry key="app/src/main/res/layout/sample_credits_tab.xml" value="0.25" />
         <entry key="app/src/main/res/layout/sample_instrument_view.xml" value="0.33" />
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index f3d36ec..e666dca 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -1,7 +1,6 @@
 package com.lukas.music.song
 
 import com.lukas.music.instruments.Instrument
-import com.lukas.music.song.chords.Chord
 import com.lukas.music.song.chords.ChordProgression
 import com.lukas.music.song.note.Note
 import com.lukas.music.util.Cycle
@@ -11,22 +10,19 @@
     val beats: Int
 ) : Cycle<Int>(beats) {
     val chordProgression = ChordProgression()
-    private lateinit var chord: Chord
 
     init {
         for (i in 0 until beats) {
             this += i
         }
         wraparoundListeners += {
-            chordProgression.step()?.let { chord = it.currentItem }
+            chordProgression.step()
         }
     }
 
     override fun step(): Int {
         super.step()
-        if (!this::chord.isInitialized) {
-            return index
-        }
+        val chord = chordProgression.currentItem?.currentItem ?: return index
         val chordNotes = chord.getNotes(root)
         for (voice in Instrument.voice) {
             voice.step(root, chordNotes)

diff --git a/.idea/misc.xml b/.idea/misc.xml
index e3b7c48..5c9d3cd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -12,7 +12,7 @@
         <entry key="app/src/main/res/layout/fragment_instrument.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_instrument_list.xml" value="0.2805755395683453" />
         <entry key="app/src/main/res/layout/fragment_main.xml" value="0.1" />
-        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.24644549763033174" />
+        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_song.xml" value="0.24644549763033174" />
         <entry key="app/src/main/res/layout/sample_credits_tab.xml" value="0.25" />
         <entry key="app/src/main/res/layout/sample_instrument_view.xml" value="0.33" />
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index f3d36ec..e666dca 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -1,7 +1,6 @@
 package com.lukas.music.song
 
 import com.lukas.music.instruments.Instrument
-import com.lukas.music.song.chords.Chord
 import com.lukas.music.song.chords.ChordProgression
 import com.lukas.music.song.note.Note
 import com.lukas.music.util.Cycle
@@ -11,22 +10,19 @@
     val beats: Int
 ) : Cycle<Int>(beats) {
     val chordProgression = ChordProgression()
-    private lateinit var chord: Chord
 
     init {
         for (i in 0 until beats) {
             this += i
         }
         wraparoundListeners += {
-            chordProgression.step()?.let { chord = it.currentItem }
+            chordProgression.step()
         }
     }
 
     override fun step(): Int {
         super.step()
-        if (!this::chord.isInitialized) {
-            return index
-        }
+        val chord = chordProgression.currentItem?.currentItem ?: return index
         val chordNotes = chord.getNotes(root)
         for (voice in Instrument.voice) {
             voice.step(root, chordNotes)
diff --git a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
index 597c100..ba3d0b1 100644
--- a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
+++ b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
@@ -31,6 +31,23 @@
                 if (Rhythm.on) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play
             )
         }
+        binding.advancePhraseButton.setOnClickListener {
+            Song.currentSong.chordProgression.bigStep(true)
+        }
+        binding.reversePhraseButton.setOnClickListener {
+            Song.currentSong.chordProgression.bigReverse(true)
+        }
+        binding.advanceMeasureButton.setOnClickListener {
+            Song.currentSong.chordProgression.step()
+        }
+        binding.reverseMeasureButton.setOnClickListener {
+            Song.currentSong.chordProgression.currentItem?.let {
+                chordDisplays[it.index].setCardBackgroundColor(
+                    ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
+                )
+            }
+            Song.currentSong.chordProgression.reverse()
+        }
         setupSlider(binding.masterVolumeSlider, 0, 100, 100) {
             setMasterVolume(it.toDouble() / 100.0)
             binding.masterVolumeText.text = "Master volume: $it%"
@@ -80,13 +97,10 @@
         if (chordDisplays.isEmpty()) {
             putChords()
         }
-        chordDisplays[Song.currentSong.chordProgression.currentItem.index].setCardBackgroundColor(
+        chordDisplays[Song.currentSong.chordProgression.currentItem!!.index].setCardBackgroundColor(
             ContextCompat.getColor(binding.root.context, R.color.purple_700)
         )
-        if (Song.currentSong.chordProgression.currentItem.index == 0) {
-            return
-        }
-        chordDisplays[Song.currentSong.chordProgression.currentItem.indexBehind].setCardBackgroundColor(
+        chordDisplays[Song.currentSong.chordProgression.currentItem!!.indexBehind].setCardBackgroundColor(
             ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
         )
     }
@@ -121,7 +135,7 @@
     private fun putChords() {
         binding.phraseDisplay.removeAllViews()
         chordDisplays.clear()
-        for (chord in Song.currentSong.chordProgression.currentItem) {
+        for (chord in Song.currentSong.chordProgression.currentItem ?: return) {
             val card = CardView(binding.root.context)
             card.layoutParams = SongFragment.tableRowLayout
             card.radius = 10f

diff --git a/.idea/misc.xml b/.idea/misc.xml
index e3b7c48..5c9d3cd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -12,7 +12,7 @@
         <entry key="app/src/main/res/layout/fragment_instrument.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_instrument_list.xml" value="0.2805755395683453" />
         <entry key="app/src/main/res/layout/fragment_main.xml" value="0.1" />
-        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.24644549763033174" />
+        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_song.xml" value="0.24644549763033174" />
         <entry key="app/src/main/res/layout/sample_credits_tab.xml" value="0.25" />
         <entry key="app/src/main/res/layout/sample_instrument_view.xml" value="0.33" />
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index f3d36ec..e666dca 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -1,7 +1,6 @@
 package com.lukas.music.song
 
 import com.lukas.music.instruments.Instrument
-import com.lukas.music.song.chords.Chord
 import com.lukas.music.song.chords.ChordProgression
 import com.lukas.music.song.note.Note
 import com.lukas.music.util.Cycle
@@ -11,22 +10,19 @@
     val beats: Int
 ) : Cycle<Int>(beats) {
     val chordProgression = ChordProgression()
-    private lateinit var chord: Chord
 
     init {
         for (i in 0 until beats) {
             this += i
         }
         wraparoundListeners += {
-            chordProgression.step()?.let { chord = it.currentItem }
+            chordProgression.step()
         }
     }
 
     override fun step(): Int {
         super.step()
-        if (!this::chord.isInitialized) {
-            return index
-        }
+        val chord = chordProgression.currentItem?.currentItem ?: return index
         val chordNotes = chord.getNotes(root)
         for (voice in Instrument.voice) {
             voice.step(root, chordNotes)
diff --git a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
index 597c100..ba3d0b1 100644
--- a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
+++ b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
@@ -31,6 +31,23 @@
                 if (Rhythm.on) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play
             )
         }
+        binding.advancePhraseButton.setOnClickListener {
+            Song.currentSong.chordProgression.bigStep(true)
+        }
+        binding.reversePhraseButton.setOnClickListener {
+            Song.currentSong.chordProgression.bigReverse(true)
+        }
+        binding.advanceMeasureButton.setOnClickListener {
+            Song.currentSong.chordProgression.step()
+        }
+        binding.reverseMeasureButton.setOnClickListener {
+            Song.currentSong.chordProgression.currentItem?.let {
+                chordDisplays[it.index].setCardBackgroundColor(
+                    ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
+                )
+            }
+            Song.currentSong.chordProgression.reverse()
+        }
         setupSlider(binding.masterVolumeSlider, 0, 100, 100) {
             setMasterVolume(it.toDouble() / 100.0)
             binding.masterVolumeText.text = "Master volume: $it%"
@@ -80,13 +97,10 @@
         if (chordDisplays.isEmpty()) {
             putChords()
         }
-        chordDisplays[Song.currentSong.chordProgression.currentItem.index].setCardBackgroundColor(
+        chordDisplays[Song.currentSong.chordProgression.currentItem!!.index].setCardBackgroundColor(
             ContextCompat.getColor(binding.root.context, R.color.purple_700)
         )
-        if (Song.currentSong.chordProgression.currentItem.index == 0) {
-            return
-        }
-        chordDisplays[Song.currentSong.chordProgression.currentItem.indexBehind].setCardBackgroundColor(
+        chordDisplays[Song.currentSong.chordProgression.currentItem!!.indexBehind].setCardBackgroundColor(
             ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
         )
     }
@@ -121,7 +135,7 @@
     private fun putChords() {
         binding.phraseDisplay.removeAllViews()
         chordDisplays.clear()
-        for (chord in Song.currentSong.chordProgression.currentItem) {
+        for (chord in Song.currentSong.chordProgression.currentItem ?: return) {
             val card = CardView(binding.root.context)
             card.layoutParams = SongFragment.tableRowLayout
             card.radius = 10f
diff --git a/app/src/main/java/com/lukas/music/util/Cycle.kt b/app/src/main/java/com/lukas/music/util/Cycle.kt
index 4bf3ec5..7e49239 100644
--- a/app/src/main/java/com/lukas/music/util/Cycle.kt
+++ b/app/src/main/java/com/lukas/music/util/Cycle.kt
@@ -8,8 +8,8 @@
     val indexBehind: Int
         get() = (index - 1 + size) % size
 
-    val currentItem: T
-        get() = this[index]
+    val currentItem: T?
+        get() = if (size == 0) null else this[index]
 
     open fun step(): T? {
         if (size == 0) {
@@ -28,6 +28,22 @@
         return this[index]
     }
 
+    open fun reset() {
+        index = size - 1
+        step()
+    }
+
+    open fun reverse() {
+        if (size == 0) {
+            return
+        }
+        index = indexBehind
+        // TODO: back around handlers
+        for (callback in stepCallback) {
+            callback()
+        }
+    }
+
     fun lookahead(distance: Int): T {
         return this[(index + distance) % size]
     }
@@ -53,4 +69,52 @@
         }
         return this[index]
     }
+
+    override fun reset() {
+        this[index].reset()
+        super.reset()
+    }
+
+    fun bigStep(keepSubindex: Boolean = false) {
+        val subindex = currentItem?.index ?: return
+        currentItem?.index = currentItem!!.size - 1
+        step()
+        if (keepSubindex) {
+            currentItem?.index = subindex
+            currentItem?.index = currentItem?.indexBehind ?: return
+            currentItem?.step()
+            for (callback in miniStepCallback) {
+                callback()
+            }
+        }
+    }
+
+    override fun reverse() {
+        currentItem?.reverse() ?: return
+        if (currentItem!!.index == currentItem!!.size - 1) {
+            currentItem!!.reset()
+            super.reverse()
+            currentItem!!.index = currentItem!!.size - 1
+        }
+        for (callback in miniStepCallback) {
+            callback()
+        }
+    }
+
+    fun bigReverse(keepSubindex: Boolean = false) {
+        val subindex = currentItem?.index ?: return
+        currentItem?.reset()
+        index = indexBehind
+        index = indexBehind
+        currentItem?.index = currentItem!!.size - 1
+        step()
+        if (keepSubindex) {
+            currentItem?.index = subindex
+            currentItem?.index = currentItem!!.indexBehind
+            currentItem?.step()
+            for (callback in miniStepCallback) {
+                callback()
+            }
+        }
+    }
 }
\ No newline at end of file

diff --git a/.idea/misc.xml b/.idea/misc.xml
index e3b7c48..5c9d3cd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -12,7 +12,7 @@
         <entry key="app/src/main/res/layout/fragment_instrument.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_instrument_list.xml" value="0.2805755395683453" />
         <entry key="app/src/main/res/layout/fragment_main.xml" value="0.1" />
-        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.24644549763033174" />
+        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_song.xml" value="0.24644549763033174" />
         <entry key="app/src/main/res/layout/sample_credits_tab.xml" value="0.25" />
         <entry key="app/src/main/res/layout/sample_instrument_view.xml" value="0.33" />
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index f3d36ec..e666dca 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -1,7 +1,6 @@
 package com.lukas.music.song
 
 import com.lukas.music.instruments.Instrument
-import com.lukas.music.song.chords.Chord
 import com.lukas.music.song.chords.ChordProgression
 import com.lukas.music.song.note.Note
 import com.lukas.music.util.Cycle
@@ -11,22 +10,19 @@
     val beats: Int
 ) : Cycle<Int>(beats) {
     val chordProgression = ChordProgression()
-    private lateinit var chord: Chord
 
     init {
         for (i in 0 until beats) {
             this += i
         }
         wraparoundListeners += {
-            chordProgression.step()?.let { chord = it.currentItem }
+            chordProgression.step()
         }
     }
 
     override fun step(): Int {
         super.step()
-        if (!this::chord.isInitialized) {
-            return index
-        }
+        val chord = chordProgression.currentItem?.currentItem ?: return index
         val chordNotes = chord.getNotes(root)
         for (voice in Instrument.voice) {
             voice.step(root, chordNotes)
diff --git a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
index 597c100..ba3d0b1 100644
--- a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
+++ b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
@@ -31,6 +31,23 @@
                 if (Rhythm.on) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play
             )
         }
+        binding.advancePhraseButton.setOnClickListener {
+            Song.currentSong.chordProgression.bigStep(true)
+        }
+        binding.reversePhraseButton.setOnClickListener {
+            Song.currentSong.chordProgression.bigReverse(true)
+        }
+        binding.advanceMeasureButton.setOnClickListener {
+            Song.currentSong.chordProgression.step()
+        }
+        binding.reverseMeasureButton.setOnClickListener {
+            Song.currentSong.chordProgression.currentItem?.let {
+                chordDisplays[it.index].setCardBackgroundColor(
+                    ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
+                )
+            }
+            Song.currentSong.chordProgression.reverse()
+        }
         setupSlider(binding.masterVolumeSlider, 0, 100, 100) {
             setMasterVolume(it.toDouble() / 100.0)
             binding.masterVolumeText.text = "Master volume: $it%"
@@ -80,13 +97,10 @@
         if (chordDisplays.isEmpty()) {
             putChords()
         }
-        chordDisplays[Song.currentSong.chordProgression.currentItem.index].setCardBackgroundColor(
+        chordDisplays[Song.currentSong.chordProgression.currentItem!!.index].setCardBackgroundColor(
             ContextCompat.getColor(binding.root.context, R.color.purple_700)
         )
-        if (Song.currentSong.chordProgression.currentItem.index == 0) {
-            return
-        }
-        chordDisplays[Song.currentSong.chordProgression.currentItem.indexBehind].setCardBackgroundColor(
+        chordDisplays[Song.currentSong.chordProgression.currentItem!!.indexBehind].setCardBackgroundColor(
             ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
         )
     }
@@ -121,7 +135,7 @@
     private fun putChords() {
         binding.phraseDisplay.removeAllViews()
         chordDisplays.clear()
-        for (chord in Song.currentSong.chordProgression.currentItem) {
+        for (chord in Song.currentSong.chordProgression.currentItem ?: return) {
             val card = CardView(binding.root.context)
             card.layoutParams = SongFragment.tableRowLayout
             card.radius = 10f
diff --git a/app/src/main/java/com/lukas/music/util/Cycle.kt b/app/src/main/java/com/lukas/music/util/Cycle.kt
index 4bf3ec5..7e49239 100644
--- a/app/src/main/java/com/lukas/music/util/Cycle.kt
+++ b/app/src/main/java/com/lukas/music/util/Cycle.kt
@@ -8,8 +8,8 @@
     val indexBehind: Int
         get() = (index - 1 + size) % size
 
-    val currentItem: T
-        get() = this[index]
+    val currentItem: T?
+        get() = if (size == 0) null else this[index]
 
     open fun step(): T? {
         if (size == 0) {
@@ -28,6 +28,22 @@
         return this[index]
     }
 
+    open fun reset() {
+        index = size - 1
+        step()
+    }
+
+    open fun reverse() {
+        if (size == 0) {
+            return
+        }
+        index = indexBehind
+        // TODO: back around handlers
+        for (callback in stepCallback) {
+            callback()
+        }
+    }
+
     fun lookahead(distance: Int): T {
         return this[(index + distance) % size]
     }
@@ -53,4 +69,52 @@
         }
         return this[index]
     }
+
+    override fun reset() {
+        this[index].reset()
+        super.reset()
+    }
+
+    fun bigStep(keepSubindex: Boolean = false) {
+        val subindex = currentItem?.index ?: return
+        currentItem?.index = currentItem!!.size - 1
+        step()
+        if (keepSubindex) {
+            currentItem?.index = subindex
+            currentItem?.index = currentItem?.indexBehind ?: return
+            currentItem?.step()
+            for (callback in miniStepCallback) {
+                callback()
+            }
+        }
+    }
+
+    override fun reverse() {
+        currentItem?.reverse() ?: return
+        if (currentItem!!.index == currentItem!!.size - 1) {
+            currentItem!!.reset()
+            super.reverse()
+            currentItem!!.index = currentItem!!.size - 1
+        }
+        for (callback in miniStepCallback) {
+            callback()
+        }
+    }
+
+    fun bigReverse(keepSubindex: Boolean = false) {
+        val subindex = currentItem?.index ?: return
+        currentItem?.reset()
+        index = indexBehind
+        index = indexBehind
+        currentItem?.index = currentItem!!.size - 1
+        step()
+        if (keepSubindex) {
+            currentItem?.index = subindex
+            currentItem?.index = currentItem!!.indexBehind
+            currentItem?.step()
+            for (callback in miniStepCallback) {
+                callback()
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_play.xml b/app/src/main/res/layout/fragment_play.xml
index fe27cd5..e4182bf 100644
--- a/app/src/main/res/layout/fragment_play.xml
+++ b/app/src/main/res/layout/fragment_play.xml
@@ -33,6 +33,63 @@
         tools:layout_conversion_absoluteHeight="56dp"
         tools:layout_conversion_absoluteWidth="56dp" />
 
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/reverseMeasureButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        android:layout_marginEnd="16dp"
+        android:clickable="true"
+        android:contentDescription="@string/reverse_measure"
+        android:src="@android:drawable/ic_media_previous"
+        app:fabSize="mini"
+        app:layout_constraintEnd_toStartOf="@+id/playButton"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:layout_conversion_absoluteHeight="56dp"
+        tools:layout_conversion_absoluteWidth="56dp" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/reversePhraseButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:clickable="true"
+        android:contentDescription="@string/reverse_phrase"
+        android:src="@android:drawable/ic_media_rew"
+        app:fabSize="mini"
+        app:layout_constraintEnd_toStartOf="@+id/reverseMeasureButton"
+        app:layout_constraintTop_toTopOf="@+id/reverseMeasureButton"
+        tools:layout_conversion_absoluteHeight="56dp"
+        tools:layout_conversion_absoluteWidth="56dp" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/advanceMeasureButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:clickable="true"
+        android:contentDescription="@string/advance_measure"
+        android:src="@android:drawable/ic_media_next"
+        app:fabSize="mini"
+        app:layout_constraintStart_toEndOf="@+id/playButton"
+        app:layout_constraintTop_toTopOf="@+id/reverseMeasureButton"
+        tools:layout_conversion_absoluteHeight="56dp"
+        tools:layout_conversion_absoluteWidth="56dp" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/advancePhraseButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:clickable="true"
+        android:contentDescription="@string/advance_phrase"
+        android:src="@android:drawable/ic_media_ff"
+        app:fabSize="mini"
+        app:layout_constraintStart_toEndOf="@+id/advanceMeasureButton"
+        app:layout_constraintTop_toTopOf="@+id/reverseMeasureButton"
+        tools:layout_conversion_absoluteHeight="56dp"
+        tools:layout_conversion_absoluteWidth="56dp" />
+
     <TextView
         android:id="@+id/tempoText"
         android:layout_width="wrap_content"
@@ -122,7 +179,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_margin="10dp"
-            android:text="Next chord not known yet . . ."
+            android:text="@string/unknown_chord"
             android:textSize="20sp"
             tools:layout_conversion_absoluteHeight="27dp"
             tools:layout_conversion_absoluteWidth="258dp" />

diff --git a/.idea/misc.xml b/.idea/misc.xml
index e3b7c48..5c9d3cd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -12,7 +12,7 @@
         <entry key="app/src/main/res/layout/fragment_instrument.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_instrument_list.xml" value="0.2805755395683453" />
         <entry key="app/src/main/res/layout/fragment_main.xml" value="0.1" />
-        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.24644549763033174" />
+        <entry key="app/src/main/res/layout/fragment_play.xml" value="0.33" />
         <entry key="app/src/main/res/layout/fragment_song.xml" value="0.24644549763033174" />
         <entry key="app/src/main/res/layout/sample_credits_tab.xml" value="0.25" />
         <entry key="app/src/main/res/layout/sample_instrument_view.xml" value="0.33" />
diff --git a/app/src/main/java/com/lukas/music/song/Song.kt b/app/src/main/java/com/lukas/music/song/Song.kt
index f3d36ec..e666dca 100644
--- a/app/src/main/java/com/lukas/music/song/Song.kt
+++ b/app/src/main/java/com/lukas/music/song/Song.kt
@@ -1,7 +1,6 @@
 package com.lukas.music.song
 
 import com.lukas.music.instruments.Instrument
-import com.lukas.music.song.chords.Chord
 import com.lukas.music.song.chords.ChordProgression
 import com.lukas.music.song.note.Note
 import com.lukas.music.util.Cycle
@@ -11,22 +10,19 @@
     val beats: Int
 ) : Cycle<Int>(beats) {
     val chordProgression = ChordProgression()
-    private lateinit var chord: Chord
 
     init {
         for (i in 0 until beats) {
             this += i
         }
         wraparoundListeners += {
-            chordProgression.step()?.let { chord = it.currentItem }
+            chordProgression.step()
         }
     }
 
     override fun step(): Int {
         super.step()
-        if (!this::chord.isInitialized) {
-            return index
-        }
+        val chord = chordProgression.currentItem?.currentItem ?: return index
         val chordNotes = chord.getNotes(root)
         for (voice in Instrument.voice) {
             voice.step(root, chordNotes)
diff --git a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
index 597c100..ba3d0b1 100644
--- a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
+++ b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt
@@ -31,6 +31,23 @@
                 if (Rhythm.on) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play
             )
         }
+        binding.advancePhraseButton.setOnClickListener {
+            Song.currentSong.chordProgression.bigStep(true)
+        }
+        binding.reversePhraseButton.setOnClickListener {
+            Song.currentSong.chordProgression.bigReverse(true)
+        }
+        binding.advanceMeasureButton.setOnClickListener {
+            Song.currentSong.chordProgression.step()
+        }
+        binding.reverseMeasureButton.setOnClickListener {
+            Song.currentSong.chordProgression.currentItem?.let {
+                chordDisplays[it.index].setCardBackgroundColor(
+                    ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
+                )
+            }
+            Song.currentSong.chordProgression.reverse()
+        }
         setupSlider(binding.masterVolumeSlider, 0, 100, 100) {
             setMasterVolume(it.toDouble() / 100.0)
             binding.masterVolumeText.text = "Master volume: $it%"
@@ -80,13 +97,10 @@
         if (chordDisplays.isEmpty()) {
             putChords()
         }
-        chordDisplays[Song.currentSong.chordProgression.currentItem.index].setCardBackgroundColor(
+        chordDisplays[Song.currentSong.chordProgression.currentItem!!.index].setCardBackgroundColor(
             ContextCompat.getColor(binding.root.context, R.color.purple_700)
         )
-        if (Song.currentSong.chordProgression.currentItem.index == 0) {
-            return
-        }
-        chordDisplays[Song.currentSong.chordProgression.currentItem.indexBehind].setCardBackgroundColor(
+        chordDisplays[Song.currentSong.chordProgression.currentItem!!.indexBehind].setCardBackgroundColor(
             ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
         )
     }
@@ -121,7 +135,7 @@
     private fun putChords() {
         binding.phraseDisplay.removeAllViews()
         chordDisplays.clear()
-        for (chord in Song.currentSong.chordProgression.currentItem) {
+        for (chord in Song.currentSong.chordProgression.currentItem ?: return) {
             val card = CardView(binding.root.context)
             card.layoutParams = SongFragment.tableRowLayout
             card.radius = 10f
diff --git a/app/src/main/java/com/lukas/music/util/Cycle.kt b/app/src/main/java/com/lukas/music/util/Cycle.kt
index 4bf3ec5..7e49239 100644
--- a/app/src/main/java/com/lukas/music/util/Cycle.kt
+++ b/app/src/main/java/com/lukas/music/util/Cycle.kt
@@ -8,8 +8,8 @@
     val indexBehind: Int
         get() = (index - 1 + size) % size
 
-    val currentItem: T
-        get() = this[index]
+    val currentItem: T?
+        get() = if (size == 0) null else this[index]
 
     open fun step(): T? {
         if (size == 0) {
@@ -28,6 +28,22 @@
         return this[index]
     }
 
+    open fun reset() {
+        index = size - 1
+        step()
+    }
+
+    open fun reverse() {
+        if (size == 0) {
+            return
+        }
+        index = indexBehind
+        // TODO: back around handlers
+        for (callback in stepCallback) {
+            callback()
+        }
+    }
+
     fun lookahead(distance: Int): T {
         return this[(index + distance) % size]
     }
@@ -53,4 +69,52 @@
         }
         return this[index]
     }
+
+    override fun reset() {
+        this[index].reset()
+        super.reset()
+    }
+
+    fun bigStep(keepSubindex: Boolean = false) {
+        val subindex = currentItem?.index ?: return
+        currentItem?.index = currentItem!!.size - 1
+        step()
+        if (keepSubindex) {
+            currentItem?.index = subindex
+            currentItem?.index = currentItem?.indexBehind ?: return
+            currentItem?.step()
+            for (callback in miniStepCallback) {
+                callback()
+            }
+        }
+    }
+
+    override fun reverse() {
+        currentItem?.reverse() ?: return
+        if (currentItem!!.index == currentItem!!.size - 1) {
+            currentItem!!.reset()
+            super.reverse()
+            currentItem!!.index = currentItem!!.size - 1
+        }
+        for (callback in miniStepCallback) {
+            callback()
+        }
+    }
+
+    fun bigReverse(keepSubindex: Boolean = false) {
+        val subindex = currentItem?.index ?: return
+        currentItem?.reset()
+        index = indexBehind
+        index = indexBehind
+        currentItem?.index = currentItem!!.size - 1
+        step()
+        if (keepSubindex) {
+            currentItem?.index = subindex
+            currentItem?.index = currentItem!!.indexBehind
+            currentItem?.step()
+            for (callback in miniStepCallback) {
+                callback()
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_play.xml b/app/src/main/res/layout/fragment_play.xml
index fe27cd5..e4182bf 100644
--- a/app/src/main/res/layout/fragment_play.xml
+++ b/app/src/main/res/layout/fragment_play.xml
@@ -33,6 +33,63 @@
         tools:layout_conversion_absoluteHeight="56dp"
         tools:layout_conversion_absoluteWidth="56dp" />
 
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/reverseMeasureButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        android:layout_marginEnd="16dp"
+        android:clickable="true"
+        android:contentDescription="@string/reverse_measure"
+        android:src="@android:drawable/ic_media_previous"
+        app:fabSize="mini"
+        app:layout_constraintEnd_toStartOf="@+id/playButton"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:layout_conversion_absoluteHeight="56dp"
+        tools:layout_conversion_absoluteWidth="56dp" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/reversePhraseButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:clickable="true"
+        android:contentDescription="@string/reverse_phrase"
+        android:src="@android:drawable/ic_media_rew"
+        app:fabSize="mini"
+        app:layout_constraintEnd_toStartOf="@+id/reverseMeasureButton"
+        app:layout_constraintTop_toTopOf="@+id/reverseMeasureButton"
+        tools:layout_conversion_absoluteHeight="56dp"
+        tools:layout_conversion_absoluteWidth="56dp" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/advanceMeasureButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:clickable="true"
+        android:contentDescription="@string/advance_measure"
+        android:src="@android:drawable/ic_media_next"
+        app:fabSize="mini"
+        app:layout_constraintStart_toEndOf="@+id/playButton"
+        app:layout_constraintTop_toTopOf="@+id/reverseMeasureButton"
+        tools:layout_conversion_absoluteHeight="56dp"
+        tools:layout_conversion_absoluteWidth="56dp" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/advancePhraseButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:clickable="true"
+        android:contentDescription="@string/advance_phrase"
+        android:src="@android:drawable/ic_media_ff"
+        app:fabSize="mini"
+        app:layout_constraintStart_toEndOf="@+id/advanceMeasureButton"
+        app:layout_constraintTop_toTopOf="@+id/reverseMeasureButton"
+        tools:layout_conversion_absoluteHeight="56dp"
+        tools:layout_conversion_absoluteWidth="56dp" />
+
     <TextView
         android:id="@+id/tempoText"
         android:layout_width="wrap_content"
@@ -122,7 +179,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_margin="10dp"
-            android:text="Next chord not known yet . . ."
+            android:text="@string/unknown_chord"
             android:textSize="20sp"
             tools:layout_conversion_absoluteHeight="27dp"
             tools:layout_conversion_absoluteWidth="258dp" />
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1ba02a4..c125463 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -19,4 +19,9 @@
     <string name="chord_pitch">Chord pitch</string>
     <string name="chord_type">Chord type</string>
     <string name="exit">Exit this menu</string>
+    <string name="advance_measure">Go to the next measure</string>
+    <string name="reverse_measure">Go to the start of the current measure</string>
+    <string name="advance_phrase">Go to the next phrase</string>
+    <string name="reverse_phrase">Go to the start of the current phrase</string>
+    <string name="unknown_chord">Next chord not known yet . . .</string>
 </resources>
\ No newline at end of file