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 ed56f52..f3d36ec 100644 --- a/app/src/main/java/com/lukas/music/song/Song.kt +++ b/app/src/main/java/com/lukas/music/song/Song.kt @@ -1,71 +1,43 @@ package com.lukas.music.song -import android.os.Handler -import android.os.Looper -import android.view.View 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.chords.Phrase import com.lukas.music.song.note.Note +import com.lukas.music.util.Cycle class Song( var root: Note, val beats: Int -) { - val chordProgression = ChordProgression(this) - private var beat = beats - 1 +) : Cycle(beats) { + val chordProgression = ChordProgression() private lateinit var chord: Chord - fun step() { - if (chordProgression.phrases.isEmpty()) { - return + init { + for (i in 0 until beats) { + this += i } - Handler(Looper.getMainLooper()).post { - val before = beat - beat++ - if (beat >= beats) { - beat = 0 - var oldChord: Chord? = null - if (this::chord.isInitialized) { - oldChord = chord - } - chord = chordProgression.step() - for (callback in chordCallback) { - callback(oldChord ?: chord, chord) - } - } - for (callback in beatCallback) { - callback(before, beat) - } - // this should not be executed here, but otherwise timing problems show up... - val chordNotes = chord.getNotes(root) - for (voice in Instrument.voice) { - voice.step(root, chordNotes) - } + wraparoundListeners += { + chordProgression.step()?.let { chord = it.currentItem } } } + override fun step(): Int { + super.step() + if (!this::chord.isInitialized) { + return index + } + val chordNotes = chord.getNotes(root) + for (voice in Instrument.voice) { + voice.step(root, chordNotes) + } + return index + } + companion object { var currentSong = Song( Note.NOTES[69], 4 ) - - private val beatCallback = mutableListOf<(Int, Int) -> Unit>() - private val chordCallback = mutableListOf<(Chord, Chord) -> Unit>() - val phraseCallback = mutableListOf<(Phrase) -> Unit>() - - fun View.setOnBeatCallback(callback: (Int, Int) -> Unit) { - beatCallback += callback - } - - fun View.setOnPhraseCallback(callback: (Phrase) -> Unit) { - phraseCallback += callback - } - - fun View.setOnChordCallback(callback: (Chord, Chord) -> Unit) { - chordCallback += callback - } } } \ No newline at end of file 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 ed56f52..f3d36ec 100644 --- a/app/src/main/java/com/lukas/music/song/Song.kt +++ b/app/src/main/java/com/lukas/music/song/Song.kt @@ -1,71 +1,43 @@ package com.lukas.music.song -import android.os.Handler -import android.os.Looper -import android.view.View 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.chords.Phrase import com.lukas.music.song.note.Note +import com.lukas.music.util.Cycle class Song( var root: Note, val beats: Int -) { - val chordProgression = ChordProgression(this) - private var beat = beats - 1 +) : Cycle(beats) { + val chordProgression = ChordProgression() private lateinit var chord: Chord - fun step() { - if (chordProgression.phrases.isEmpty()) { - return + init { + for (i in 0 until beats) { + this += i } - Handler(Looper.getMainLooper()).post { - val before = beat - beat++ - if (beat >= beats) { - beat = 0 - var oldChord: Chord? = null - if (this::chord.isInitialized) { - oldChord = chord - } - chord = chordProgression.step() - for (callback in chordCallback) { - callback(oldChord ?: chord, chord) - } - } - for (callback in beatCallback) { - callback(before, beat) - } - // this should not be executed here, but otherwise timing problems show up... - val chordNotes = chord.getNotes(root) - for (voice in Instrument.voice) { - voice.step(root, chordNotes) - } + wraparoundListeners += { + chordProgression.step()?.let { chord = it.currentItem } } } + override fun step(): Int { + super.step() + if (!this::chord.isInitialized) { + return index + } + val chordNotes = chord.getNotes(root) + for (voice in Instrument.voice) { + voice.step(root, chordNotes) + } + return index + } + companion object { var currentSong = Song( Note.NOTES[69], 4 ) - - private val beatCallback = mutableListOf<(Int, Int) -> Unit>() - private val chordCallback = mutableListOf<(Chord, Chord) -> Unit>() - val phraseCallback = mutableListOf<(Phrase) -> Unit>() - - fun View.setOnBeatCallback(callback: (Int, Int) -> Unit) { - beatCallback += callback - } - - fun View.setOnPhraseCallback(callback: (Phrase) -> Unit) { - phraseCallback += callback - } - - fun View.setOnChordCallback(callback: (Chord, Chord) -> Unit) { - chordCallback += callback - } } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt index 56e42ec..86ebdf1 100644 --- a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt +++ b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt @@ -1,24 +1,5 @@ package com.lukas.music.song.chords -import com.lukas.music.song.Song +import com.lukas.music.util.MetaCycle -class ChordProgression(val song: Song) { - // TODO: special handler for increasing or decreasing measuresPerPhrase - val measuresPerPhrase: Int = 4 - val phrases = mutableListOf() - - private var position = 0 - fun step(): Chord { - val phrase = phrases[position] - return phrase.step(this) - } - - operator fun inc(): ChordProgression { - position++ - position %= phrases.size - for (callback in Song.phraseCallback) { - callback(phrases[position]) - } - return this - } -} \ No newline at end of file +class ChordProgression : MetaCycle() \ No newline at end of file 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 ed56f52..f3d36ec 100644 --- a/app/src/main/java/com/lukas/music/song/Song.kt +++ b/app/src/main/java/com/lukas/music/song/Song.kt @@ -1,71 +1,43 @@ package com.lukas.music.song -import android.os.Handler -import android.os.Looper -import android.view.View 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.chords.Phrase import com.lukas.music.song.note.Note +import com.lukas.music.util.Cycle class Song( var root: Note, val beats: Int -) { - val chordProgression = ChordProgression(this) - private var beat = beats - 1 +) : Cycle(beats) { + val chordProgression = ChordProgression() private lateinit var chord: Chord - fun step() { - if (chordProgression.phrases.isEmpty()) { - return + init { + for (i in 0 until beats) { + this += i } - Handler(Looper.getMainLooper()).post { - val before = beat - beat++ - if (beat >= beats) { - beat = 0 - var oldChord: Chord? = null - if (this::chord.isInitialized) { - oldChord = chord - } - chord = chordProgression.step() - for (callback in chordCallback) { - callback(oldChord ?: chord, chord) - } - } - for (callback in beatCallback) { - callback(before, beat) - } - // this should not be executed here, but otherwise timing problems show up... - val chordNotes = chord.getNotes(root) - for (voice in Instrument.voice) { - voice.step(root, chordNotes) - } + wraparoundListeners += { + chordProgression.step()?.let { chord = it.currentItem } } } + override fun step(): Int { + super.step() + if (!this::chord.isInitialized) { + return index + } + val chordNotes = chord.getNotes(root) + for (voice in Instrument.voice) { + voice.step(root, chordNotes) + } + return index + } + companion object { var currentSong = Song( Note.NOTES[69], 4 ) - - private val beatCallback = mutableListOf<(Int, Int) -> Unit>() - private val chordCallback = mutableListOf<(Chord, Chord) -> Unit>() - val phraseCallback = mutableListOf<(Phrase) -> Unit>() - - fun View.setOnBeatCallback(callback: (Int, Int) -> Unit) { - beatCallback += callback - } - - fun View.setOnPhraseCallback(callback: (Phrase) -> Unit) { - phraseCallback += callback - } - - fun View.setOnChordCallback(callback: (Chord, Chord) -> Unit) { - chordCallback += callback - } } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt index 56e42ec..86ebdf1 100644 --- a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt +++ b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt @@ -1,24 +1,5 @@ package com.lukas.music.song.chords -import com.lukas.music.song.Song +import com.lukas.music.util.MetaCycle -class ChordProgression(val song: Song) { - // TODO: special handler for increasing or decreasing measuresPerPhrase - val measuresPerPhrase: Int = 4 - val phrases = mutableListOf() - - private var position = 0 - fun step(): Chord { - val phrase = phrases[position] - return phrase.step(this) - } - - operator fun inc(): ChordProgression { - position++ - position %= phrases.size - for (callback in Song.phraseCallback) { - callback(phrases[position]) - } - return this - } -} \ No newline at end of file +class ChordProgression : MetaCycle() \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/song/chords/Phrase.kt b/app/src/main/java/com/lukas/music/song/chords/Phrase.kt index 611d1b8..384254f 100644 --- a/app/src/main/java/com/lukas/music/song/chords/Phrase.kt +++ b/app/src/main/java/com/lukas/music/song/chords/Phrase.kt @@ -1,22 +1,11 @@ package com.lukas.music.song.chords -class Phrase { - val chords = mutableListOf( - Chord(0, ChordType.MAJOR), - Chord(5, ChordType.MAJOR), - Chord(2, ChordType.MINOR), - Chord(7, ChordType.MAJOR), - ) +import com.lukas.music.util.Cycle - var position = 0 - fun step(parent: ChordProgression): Chord { - var parent: ChordProgression = parent - val result = chords[position] - position++ - if (position >= chords.size) { - position = 0 - parent++ +class Phrase : Cycle() { + init { + for (i in 0 until 4) { + this += Chord(0, ChordType.MAJOR) } - return result } } \ No newline at end of file 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 ed56f52..f3d36ec 100644 --- a/app/src/main/java/com/lukas/music/song/Song.kt +++ b/app/src/main/java/com/lukas/music/song/Song.kt @@ -1,71 +1,43 @@ package com.lukas.music.song -import android.os.Handler -import android.os.Looper -import android.view.View 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.chords.Phrase import com.lukas.music.song.note.Note +import com.lukas.music.util.Cycle class Song( var root: Note, val beats: Int -) { - val chordProgression = ChordProgression(this) - private var beat = beats - 1 +) : Cycle(beats) { + val chordProgression = ChordProgression() private lateinit var chord: Chord - fun step() { - if (chordProgression.phrases.isEmpty()) { - return + init { + for (i in 0 until beats) { + this += i } - Handler(Looper.getMainLooper()).post { - val before = beat - beat++ - if (beat >= beats) { - beat = 0 - var oldChord: Chord? = null - if (this::chord.isInitialized) { - oldChord = chord - } - chord = chordProgression.step() - for (callback in chordCallback) { - callback(oldChord ?: chord, chord) - } - } - for (callback in beatCallback) { - callback(before, beat) - } - // this should not be executed here, but otherwise timing problems show up... - val chordNotes = chord.getNotes(root) - for (voice in Instrument.voice) { - voice.step(root, chordNotes) - } + wraparoundListeners += { + chordProgression.step()?.let { chord = it.currentItem } } } + override fun step(): Int { + super.step() + if (!this::chord.isInitialized) { + return index + } + val chordNotes = chord.getNotes(root) + for (voice in Instrument.voice) { + voice.step(root, chordNotes) + } + return index + } + companion object { var currentSong = Song( Note.NOTES[69], 4 ) - - private val beatCallback = mutableListOf<(Int, Int) -> Unit>() - private val chordCallback = mutableListOf<(Chord, Chord) -> Unit>() - val phraseCallback = mutableListOf<(Phrase) -> Unit>() - - fun View.setOnBeatCallback(callback: (Int, Int) -> Unit) { - beatCallback += callback - } - - fun View.setOnPhraseCallback(callback: (Phrase) -> Unit) { - phraseCallback += callback - } - - fun View.setOnChordCallback(callback: (Chord, Chord) -> Unit) { - chordCallback += callback - } } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt index 56e42ec..86ebdf1 100644 --- a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt +++ b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt @@ -1,24 +1,5 @@ package com.lukas.music.song.chords -import com.lukas.music.song.Song +import com.lukas.music.util.MetaCycle -class ChordProgression(val song: Song) { - // TODO: special handler for increasing or decreasing measuresPerPhrase - val measuresPerPhrase: Int = 4 - val phrases = mutableListOf() - - private var position = 0 - fun step(): Chord { - val phrase = phrases[position] - return phrase.step(this) - } - - operator fun inc(): ChordProgression { - position++ - position %= phrases.size - for (callback in Song.phraseCallback) { - callback(phrases[position]) - } - return this - } -} \ No newline at end of file +class ChordProgression : MetaCycle() \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/song/chords/Phrase.kt b/app/src/main/java/com/lukas/music/song/chords/Phrase.kt index 611d1b8..384254f 100644 --- a/app/src/main/java/com/lukas/music/song/chords/Phrase.kt +++ b/app/src/main/java/com/lukas/music/song/chords/Phrase.kt @@ -1,22 +1,11 @@ package com.lukas.music.song.chords -class Phrase { - val chords = mutableListOf( - Chord(0, ChordType.MAJOR), - Chord(5, ChordType.MAJOR), - Chord(2, ChordType.MINOR), - Chord(7, ChordType.MAJOR), - ) +import com.lukas.music.util.Cycle - var position = 0 - fun step(parent: ChordProgression): Chord { - var parent: ChordProgression = parent - val result = chords[position] - position++ - if (position >= chords.size) { - position = 0 - parent++ +class Phrase : Cycle() { + init { + for (i in 0 until 4) { + this += Chord(0, ChordType.MAJOR) } - return result } } \ No newline at end of file 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 c8744cd..b7e6d73 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 @@ -1,6 +1,8 @@ package com.lukas.music.ui.fragments import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -12,15 +14,11 @@ import com.lukas.music.databinding.FragmentPlayBinding import com.lukas.music.instruments.Rhythm import com.lukas.music.song.Song -import com.lukas.music.song.Song.Companion.setOnBeatCallback -import com.lukas.music.song.Song.Companion.setOnChordCallback -import com.lukas.music.song.Song.Companion.setOnPhraseCallback -import com.lukas.music.song.chords.Chord class PlayFragment : Fragment() { lateinit var binding: FragmentPlayBinding private val beatIndicators = mutableListOf() - private val chordDisplays = mutableMapOf() + private val chordDisplays = mutableListOf() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -70,40 +68,56 @@ beatIndicators += child binding.beatIndicator.addView(child) } - binding.root.setOnBeatCallback { before, now -> - beatIndicators[before].isChecked = false - beatIndicators[now].isChecked = true - } - binding.root.setOnPhraseCallback { - binding.phraseDisplay.removeAllViews() - chordDisplays.clear() - for (chord in it.chords) { - val card = CardView(binding.root.context) - val text = TextView(binding.root.context) - text.text = chord.toString(true, Song.currentSong.root) - card.layoutParams = SongFragment.layout - card.addView(text) - binding.phraseDisplay.addView(card) - chordDisplays[chord] = card + Song.currentSong.stepCallback += { + Handler(Looper.getMainLooper()).post { + beatIndicators[Song.currentSong.indexBehind].isChecked = false + beatIndicators[Song.currentSong.index].isChecked = true } - binding.phraseTable.isStretchAllColumns = true } - binding.root.setOnChordCallback { old, new -> - chordDisplays[old]?.setBackgroundColor( - ContextCompat.getColor( - binding.root.context, - R.color.gray_600 + Song.currentSong.chordProgression.stepCallback += { + Handler(Looper.getMainLooper()).post { + putChords() + } + } + Song.currentSong.chordProgression.miniStepCallback += { + Handler(Looper.getMainLooper()).post { + if (chordDisplays.isEmpty()) { + putChords() + } + chordDisplays[Song.currentSong.chordProgression.currentItem.index].setBackgroundColor( + ContextCompat.getColor( + binding.root.context, + R.color.gray_400 + ) ) - ) - chordDisplays[new]?.setBackgroundColor( - ContextCompat.getColor( - binding.root.context, - R.color.gray_400 + if (Song.currentSong.chordProgression.currentItem.index == 0) { + return@post + } + chordDisplays[Song.currentSong.chordProgression.currentItem.indexBehind].setBackgroundColor( + ContextCompat.getColor( + binding.root.context, + R.color.gray_600 + ) ) - ) + } } return binding.root } + private fun putChords() { + binding.phraseDisplay.removeAllViews() + chordDisplays.clear() + for (chord in Song.currentSong.chordProgression.currentItem) { + val card = CardView(binding.root.context) + val text = TextView(binding.root.context) + text.text = chord.toString(true, Song.currentSong.root) + card.layoutParams = SongFragment.layout + card.addView(text) + binding.phraseDisplay.addView(card) + chordDisplays += card + } + binding.phraseTable.isStretchAllColumns = true + } + external fun setMasterVolume(volume: Double) } \ No newline at end of file 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 ed56f52..f3d36ec 100644 --- a/app/src/main/java/com/lukas/music/song/Song.kt +++ b/app/src/main/java/com/lukas/music/song/Song.kt @@ -1,71 +1,43 @@ package com.lukas.music.song -import android.os.Handler -import android.os.Looper -import android.view.View 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.chords.Phrase import com.lukas.music.song.note.Note +import com.lukas.music.util.Cycle class Song( var root: Note, val beats: Int -) { - val chordProgression = ChordProgression(this) - private var beat = beats - 1 +) : Cycle(beats) { + val chordProgression = ChordProgression() private lateinit var chord: Chord - fun step() { - if (chordProgression.phrases.isEmpty()) { - return + init { + for (i in 0 until beats) { + this += i } - Handler(Looper.getMainLooper()).post { - val before = beat - beat++ - if (beat >= beats) { - beat = 0 - var oldChord: Chord? = null - if (this::chord.isInitialized) { - oldChord = chord - } - chord = chordProgression.step() - for (callback in chordCallback) { - callback(oldChord ?: chord, chord) - } - } - for (callback in beatCallback) { - callback(before, beat) - } - // this should not be executed here, but otherwise timing problems show up... - val chordNotes = chord.getNotes(root) - for (voice in Instrument.voice) { - voice.step(root, chordNotes) - } + wraparoundListeners += { + chordProgression.step()?.let { chord = it.currentItem } } } + override fun step(): Int { + super.step() + if (!this::chord.isInitialized) { + return index + } + val chordNotes = chord.getNotes(root) + for (voice in Instrument.voice) { + voice.step(root, chordNotes) + } + return index + } + companion object { var currentSong = Song( Note.NOTES[69], 4 ) - - private val beatCallback = mutableListOf<(Int, Int) -> Unit>() - private val chordCallback = mutableListOf<(Chord, Chord) -> Unit>() - val phraseCallback = mutableListOf<(Phrase) -> Unit>() - - fun View.setOnBeatCallback(callback: (Int, Int) -> Unit) { - beatCallback += callback - } - - fun View.setOnPhraseCallback(callback: (Phrase) -> Unit) { - phraseCallback += callback - } - - fun View.setOnChordCallback(callback: (Chord, Chord) -> Unit) { - chordCallback += callback - } } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt index 56e42ec..86ebdf1 100644 --- a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt +++ b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt @@ -1,24 +1,5 @@ package com.lukas.music.song.chords -import com.lukas.music.song.Song +import com.lukas.music.util.MetaCycle -class ChordProgression(val song: Song) { - // TODO: special handler for increasing or decreasing measuresPerPhrase - val measuresPerPhrase: Int = 4 - val phrases = mutableListOf() - - private var position = 0 - fun step(): Chord { - val phrase = phrases[position] - return phrase.step(this) - } - - operator fun inc(): ChordProgression { - position++ - position %= phrases.size - for (callback in Song.phraseCallback) { - callback(phrases[position]) - } - return this - } -} \ No newline at end of file +class ChordProgression : MetaCycle() \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/song/chords/Phrase.kt b/app/src/main/java/com/lukas/music/song/chords/Phrase.kt index 611d1b8..384254f 100644 --- a/app/src/main/java/com/lukas/music/song/chords/Phrase.kt +++ b/app/src/main/java/com/lukas/music/song/chords/Phrase.kt @@ -1,22 +1,11 @@ package com.lukas.music.song.chords -class Phrase { - val chords = mutableListOf( - Chord(0, ChordType.MAJOR), - Chord(5, ChordType.MAJOR), - Chord(2, ChordType.MINOR), - Chord(7, ChordType.MAJOR), - ) +import com.lukas.music.util.Cycle - var position = 0 - fun step(parent: ChordProgression): Chord { - var parent: ChordProgression = parent - val result = chords[position] - position++ - if (position >= chords.size) { - position = 0 - parent++ +class Phrase : Cycle() { + init { + for (i in 0 until 4) { + this += Chord(0, ChordType.MAJOR) } - return result } } \ No newline at end of file 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 c8744cd..b7e6d73 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 @@ -1,6 +1,8 @@ package com.lukas.music.ui.fragments import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -12,15 +14,11 @@ import com.lukas.music.databinding.FragmentPlayBinding import com.lukas.music.instruments.Rhythm import com.lukas.music.song.Song -import com.lukas.music.song.Song.Companion.setOnBeatCallback -import com.lukas.music.song.Song.Companion.setOnChordCallback -import com.lukas.music.song.Song.Companion.setOnPhraseCallback -import com.lukas.music.song.chords.Chord class PlayFragment : Fragment() { lateinit var binding: FragmentPlayBinding private val beatIndicators = mutableListOf() - private val chordDisplays = mutableMapOf() + private val chordDisplays = mutableListOf() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -70,40 +68,56 @@ beatIndicators += child binding.beatIndicator.addView(child) } - binding.root.setOnBeatCallback { before, now -> - beatIndicators[before].isChecked = false - beatIndicators[now].isChecked = true - } - binding.root.setOnPhraseCallback { - binding.phraseDisplay.removeAllViews() - chordDisplays.clear() - for (chord in it.chords) { - val card = CardView(binding.root.context) - val text = TextView(binding.root.context) - text.text = chord.toString(true, Song.currentSong.root) - card.layoutParams = SongFragment.layout - card.addView(text) - binding.phraseDisplay.addView(card) - chordDisplays[chord] = card + Song.currentSong.stepCallback += { + Handler(Looper.getMainLooper()).post { + beatIndicators[Song.currentSong.indexBehind].isChecked = false + beatIndicators[Song.currentSong.index].isChecked = true } - binding.phraseTable.isStretchAllColumns = true } - binding.root.setOnChordCallback { old, new -> - chordDisplays[old]?.setBackgroundColor( - ContextCompat.getColor( - binding.root.context, - R.color.gray_600 + Song.currentSong.chordProgression.stepCallback += { + Handler(Looper.getMainLooper()).post { + putChords() + } + } + Song.currentSong.chordProgression.miniStepCallback += { + Handler(Looper.getMainLooper()).post { + if (chordDisplays.isEmpty()) { + putChords() + } + chordDisplays[Song.currentSong.chordProgression.currentItem.index].setBackgroundColor( + ContextCompat.getColor( + binding.root.context, + R.color.gray_400 + ) ) - ) - chordDisplays[new]?.setBackgroundColor( - ContextCompat.getColor( - binding.root.context, - R.color.gray_400 + if (Song.currentSong.chordProgression.currentItem.index == 0) { + return@post + } + chordDisplays[Song.currentSong.chordProgression.currentItem.indexBehind].setBackgroundColor( + ContextCompat.getColor( + binding.root.context, + R.color.gray_600 + ) ) - ) + } } return binding.root } + private fun putChords() { + binding.phraseDisplay.removeAllViews() + chordDisplays.clear() + for (chord in Song.currentSong.chordProgression.currentItem) { + val card = CardView(binding.root.context) + val text = TextView(binding.root.context) + text.text = chord.toString(true, Song.currentSong.root) + card.layoutParams = SongFragment.layout + card.addView(text) + binding.phraseDisplay.addView(card) + chordDisplays += card + } + binding.phraseTable.isStretchAllColumns = true + } + external fun setMasterVolume(volume: Double) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/SongFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/SongFragment.kt index ef0af81..ed9ab76 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/SongFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/SongFragment.kt @@ -38,7 +38,7 @@ binding.keySelection.onItemSelectedListener = this binding.keySelection.setSelection(Song.currentSong.root.noteName.index) binding.addPhraseButton.setOnClickListener { - Song.currentSong.chordProgression.phrases += Phrase() + Song.currentSong.chordProgression += Phrase() updateChords() } updateChords() @@ -47,9 +47,9 @@ fun updateChords() { binding.chords.removeAllViews() - for (phrase in Song.currentSong.chordProgression.phrases) { + for (phrase in Song.currentSong.chordProgression) { val row = TableRow(binding.root.context) - for (chord in phrase.chords) { + for (chord in phrase) { val card = CardView(binding.root.context) card.radius = 10f card.layoutParams = layout @@ -64,7 +64,7 @@ } val button = ImageButton(binding.root.context) button.setOnClickListener { - Song.currentSong.chordProgression.phrases -= phrase + Song.currentSong.chordProgression -= phrase updateChords() } button.setImageResource(android.R.drawable.ic_delete) 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 ed56f52..f3d36ec 100644 --- a/app/src/main/java/com/lukas/music/song/Song.kt +++ b/app/src/main/java/com/lukas/music/song/Song.kt @@ -1,71 +1,43 @@ package com.lukas.music.song -import android.os.Handler -import android.os.Looper -import android.view.View 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.chords.Phrase import com.lukas.music.song.note.Note +import com.lukas.music.util.Cycle class Song( var root: Note, val beats: Int -) { - val chordProgression = ChordProgression(this) - private var beat = beats - 1 +) : Cycle(beats) { + val chordProgression = ChordProgression() private lateinit var chord: Chord - fun step() { - if (chordProgression.phrases.isEmpty()) { - return + init { + for (i in 0 until beats) { + this += i } - Handler(Looper.getMainLooper()).post { - val before = beat - beat++ - if (beat >= beats) { - beat = 0 - var oldChord: Chord? = null - if (this::chord.isInitialized) { - oldChord = chord - } - chord = chordProgression.step() - for (callback in chordCallback) { - callback(oldChord ?: chord, chord) - } - } - for (callback in beatCallback) { - callback(before, beat) - } - // this should not be executed here, but otherwise timing problems show up... - val chordNotes = chord.getNotes(root) - for (voice in Instrument.voice) { - voice.step(root, chordNotes) - } + wraparoundListeners += { + chordProgression.step()?.let { chord = it.currentItem } } } + override fun step(): Int { + super.step() + if (!this::chord.isInitialized) { + return index + } + val chordNotes = chord.getNotes(root) + for (voice in Instrument.voice) { + voice.step(root, chordNotes) + } + return index + } + companion object { var currentSong = Song( Note.NOTES[69], 4 ) - - private val beatCallback = mutableListOf<(Int, Int) -> Unit>() - private val chordCallback = mutableListOf<(Chord, Chord) -> Unit>() - val phraseCallback = mutableListOf<(Phrase) -> Unit>() - - fun View.setOnBeatCallback(callback: (Int, Int) -> Unit) { - beatCallback += callback - } - - fun View.setOnPhraseCallback(callback: (Phrase) -> Unit) { - phraseCallback += callback - } - - fun View.setOnChordCallback(callback: (Chord, Chord) -> Unit) { - chordCallback += callback - } } } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt index 56e42ec..86ebdf1 100644 --- a/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt +++ b/app/src/main/java/com/lukas/music/song/chords/ChordProgression.kt @@ -1,24 +1,5 @@ package com.lukas.music.song.chords -import com.lukas.music.song.Song +import com.lukas.music.util.MetaCycle -class ChordProgression(val song: Song) { - // TODO: special handler for increasing or decreasing measuresPerPhrase - val measuresPerPhrase: Int = 4 - val phrases = mutableListOf() - - private var position = 0 - fun step(): Chord { - val phrase = phrases[position] - return phrase.step(this) - } - - operator fun inc(): ChordProgression { - position++ - position %= phrases.size - for (callback in Song.phraseCallback) { - callback(phrases[position]) - } - return this - } -} \ No newline at end of file +class ChordProgression : MetaCycle() \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/song/chords/Phrase.kt b/app/src/main/java/com/lukas/music/song/chords/Phrase.kt index 611d1b8..384254f 100644 --- a/app/src/main/java/com/lukas/music/song/chords/Phrase.kt +++ b/app/src/main/java/com/lukas/music/song/chords/Phrase.kt @@ -1,22 +1,11 @@ package com.lukas.music.song.chords -class Phrase { - val chords = mutableListOf( - Chord(0, ChordType.MAJOR), - Chord(5, ChordType.MAJOR), - Chord(2, ChordType.MINOR), - Chord(7, ChordType.MAJOR), - ) +import com.lukas.music.util.Cycle - var position = 0 - fun step(parent: ChordProgression): Chord { - var parent: ChordProgression = parent - val result = chords[position] - position++ - if (position >= chords.size) { - position = 0 - parent++ +class Phrase : Cycle() { + init { + for (i in 0 until 4) { + this += Chord(0, ChordType.MAJOR) } - return result } } \ No newline at end of file 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 c8744cd..b7e6d73 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 @@ -1,6 +1,8 @@ package com.lukas.music.ui.fragments import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -12,15 +14,11 @@ import com.lukas.music.databinding.FragmentPlayBinding import com.lukas.music.instruments.Rhythm import com.lukas.music.song.Song -import com.lukas.music.song.Song.Companion.setOnBeatCallback -import com.lukas.music.song.Song.Companion.setOnChordCallback -import com.lukas.music.song.Song.Companion.setOnPhraseCallback -import com.lukas.music.song.chords.Chord class PlayFragment : Fragment() { lateinit var binding: FragmentPlayBinding private val beatIndicators = mutableListOf() - private val chordDisplays = mutableMapOf() + private val chordDisplays = mutableListOf() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -70,40 +68,56 @@ beatIndicators += child binding.beatIndicator.addView(child) } - binding.root.setOnBeatCallback { before, now -> - beatIndicators[before].isChecked = false - beatIndicators[now].isChecked = true - } - binding.root.setOnPhraseCallback { - binding.phraseDisplay.removeAllViews() - chordDisplays.clear() - for (chord in it.chords) { - val card = CardView(binding.root.context) - val text = TextView(binding.root.context) - text.text = chord.toString(true, Song.currentSong.root) - card.layoutParams = SongFragment.layout - card.addView(text) - binding.phraseDisplay.addView(card) - chordDisplays[chord] = card + Song.currentSong.stepCallback += { + Handler(Looper.getMainLooper()).post { + beatIndicators[Song.currentSong.indexBehind].isChecked = false + beatIndicators[Song.currentSong.index].isChecked = true } - binding.phraseTable.isStretchAllColumns = true } - binding.root.setOnChordCallback { old, new -> - chordDisplays[old]?.setBackgroundColor( - ContextCompat.getColor( - binding.root.context, - R.color.gray_600 + Song.currentSong.chordProgression.stepCallback += { + Handler(Looper.getMainLooper()).post { + putChords() + } + } + Song.currentSong.chordProgression.miniStepCallback += { + Handler(Looper.getMainLooper()).post { + if (chordDisplays.isEmpty()) { + putChords() + } + chordDisplays[Song.currentSong.chordProgression.currentItem.index].setBackgroundColor( + ContextCompat.getColor( + binding.root.context, + R.color.gray_400 + ) ) - ) - chordDisplays[new]?.setBackgroundColor( - ContextCompat.getColor( - binding.root.context, - R.color.gray_400 + if (Song.currentSong.chordProgression.currentItem.index == 0) { + return@post + } + chordDisplays[Song.currentSong.chordProgression.currentItem.indexBehind].setBackgroundColor( + ContextCompat.getColor( + binding.root.context, + R.color.gray_600 + ) ) - ) + } } return binding.root } + private fun putChords() { + binding.phraseDisplay.removeAllViews() + chordDisplays.clear() + for (chord in Song.currentSong.chordProgression.currentItem) { + val card = CardView(binding.root.context) + val text = TextView(binding.root.context) + text.text = chord.toString(true, Song.currentSong.root) + card.layoutParams = SongFragment.layout + card.addView(text) + binding.phraseDisplay.addView(card) + chordDisplays += card + } + binding.phraseTable.isStretchAllColumns = true + } + external fun setMasterVolume(volume: Double) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/fragments/SongFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/SongFragment.kt index ef0af81..ed9ab76 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/SongFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/SongFragment.kt @@ -38,7 +38,7 @@ binding.keySelection.onItemSelectedListener = this binding.keySelection.setSelection(Song.currentSong.root.noteName.index) binding.addPhraseButton.setOnClickListener { - Song.currentSong.chordProgression.phrases += Phrase() + Song.currentSong.chordProgression += Phrase() updateChords() } updateChords() @@ -47,9 +47,9 @@ fun updateChords() { binding.chords.removeAllViews() - for (phrase in Song.currentSong.chordProgression.phrases) { + for (phrase in Song.currentSong.chordProgression) { val row = TableRow(binding.root.context) - for (chord in phrase.chords) { + for (chord in phrase) { val card = CardView(binding.root.context) card.radius = 10f card.layoutParams = layout @@ -64,7 +64,7 @@ } val button = ImageButton(binding.root.context) button.setOnClickListener { - Song.currentSong.chordProgression.phrases -= phrase + Song.currentSong.chordProgression -= phrase updateChords() } button.setImageResource(android.R.drawable.ic_delete) diff --git a/app/src/main/java/com/lukas/music/util/Cycle.kt b/app/src/main/java/com/lukas/music/util/Cycle.kt new file mode 100644 index 0000000..f1ef055 --- /dev/null +++ b/app/src/main/java/com/lukas/music/util/Cycle.kt @@ -0,0 +1,58 @@ +package com.lukas.music.util + +open class Cycle(initialSize: Int = 0) : ArrayList(initialSize) { + + var index = 0 + val stepCallback = mutableListOf<() -> Unit>() + val wraparoundListeners = mutableListOf<() -> Unit>() + val indexBehind: Int + get() = (index - 1 + size) % size + + val currentItem: T + get() = this[index] + + open fun step(): T? { + if (size == 0) { + return null + } + index++ + if (index >= size) { + index %= size + for (callback in wraparoundListeners) { + callback() + } + } + for (callback in stepCallback) { + callback() + } + return this[index] + } + + fun lookahead(distance: Int): T { + return this[(index + distance) % size] + } + + fun lookbehind(distance: Int): T { + return lookahead(-distance) + } +} + +open class MetaCycle> : Cycle() { + val miniStepCallback = mutableListOf<() -> Unit>() + + override fun step(): T? { + if (size == 0) { + return null + } + val callback: () -> Unit = { + super.step() + } + this[index].wraparoundListeners += callback + this[index].step() + this[index].wraparoundListeners -= callback + for (callback in miniStepCallback) { + callback() + } + return this[index] + } +} \ No newline at end of file