added tetris theme
parent
e02878f0d7
commit
e127450404
@ -1,38 +0,0 @@
|
||||
package music.Filter
|
||||
|
||||
import music.Params.SAMPLES
|
||||
import music.Signal
|
||||
|
||||
typealias Filter = (Signal) -> (Signal)
|
||||
|
||||
object Filters {
|
||||
fun easeIn(signal: Signal): Signal {
|
||||
val len = kotlin.math.min(SAMPLES / 64, signal.size)
|
||||
val start = signal.slice(0 until len)
|
||||
val end = signal.slice(len until signal.size)
|
||||
|
||||
return (start.withIndex().map { i -> (i.value * (i.index / len.toFloat())).toInt() } + end).toTypedArray();
|
||||
}
|
||||
|
||||
fun easeOut(signal: Signal): Signal {
|
||||
val len = kotlin.math.min(SAMPLES / 64, signal.size)
|
||||
val sep = signal.size - len - 1;
|
||||
val start = signal.slice(0 until sep)
|
||||
val end = signal.slice(sep until signal.size)
|
||||
|
||||
return (start + end.withIndex().map { i -> (i.value * ((len - i.index) / len.toFloat())).toInt() }).toTypedArray();
|
||||
}
|
||||
|
||||
fun ease(signal: Signal): Signal {
|
||||
val len = kotlin.math.min(SAMPLES / 64, signal.size / 2)
|
||||
val sep = signal.size - len - 1;
|
||||
val start = signal.slice(0 until len)
|
||||
val mid = signal.slice(len until sep)
|
||||
val end = signal.slice(sep until signal.size)
|
||||
|
||||
return (start.withIndex().map { i -> (i.value * (i.index / len.toFloat())).toInt() }
|
||||
+ mid
|
||||
+ end.withIndex().map { i -> (i.value * ((len - i.index) / len.toFloat())).toInt() }
|
||||
).toTypedArray();
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package music
|
||||
|
||||
import music.generators.Generator
|
||||
import music.generators.ToneGenerator
|
||||
|
||||
class Melody(val getGen: (volume: Float, tone: Int) -> (Generator), val volume: Float, val offset: Int = 0) {
|
||||
|
||||
val tones = mutableSetOf<Beat>()
|
||||
var pos = 0f
|
||||
|
||||
fun add(tone: String, len: Double, offset: Int? = null): Melody {
|
||||
return add(tone, len.toFloat(), offset)
|
||||
}
|
||||
fun add(tone: Int, len: Double, offset: Int? = null): Melody {
|
||||
return add(tone, len.toFloat(), offset)
|
||||
}
|
||||
fun add(tone: String, len: Float, offset: Int? = null): Melody {
|
||||
return add(toneFromString(tone), len, offset)
|
||||
}
|
||||
fun add(tone: Int, len: Float, offset: Int? = null): Melody {
|
||||
val ofs = offset ?: this.offset;
|
||||
tones.add(Beat(getGen(volume, tone + (ofs * 12)), len, pos))
|
||||
pos += len
|
||||
return this
|
||||
}
|
||||
|
||||
fun pause(len: Float): Melody {
|
||||
pos += len
|
||||
return this
|
||||
}
|
||||
fun pause(len: Double): Melody {
|
||||
pos += len.toFloat()
|
||||
return this
|
||||
}
|
||||
|
||||
fun applyTo(bar: Bar) {
|
||||
for (tone in tones) {
|
||||
bar.tones.add(tone)
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
pos = 0f
|
||||
tones.removeAll { true }
|
||||
}
|
||||
|
||||
companion object {
|
||||
// C C# D D# E F F# G G# A A# B C
|
||||
// C Db D Eb E F Gb G Ab A Bb B C
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
private val tones = mapOf(
|
||||
"C" to 0,
|
||||
"Db" to 1, "C#" to 1,
|
||||
"D" to 2,
|
||||
"D#" to 3, "Eb" to 3,
|
||||
"E" to 4,
|
||||
"F" to 5,
|
||||
"F#" to 6, "Gb" to 6,
|
||||
"G" to 7,
|
||||
"G#" to 8, "Ab" to 8,
|
||||
"A" to 9,
|
||||
"A#" to 10, "Bb" to 10,
|
||||
"B" to 11
|
||||
)
|
||||
|
||||
fun toneFromString(tone: String, offset: Int = 0): Int {
|
||||
return Companion.tones[tone]?.plus(offset * 12)!!
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,35 @@
|
||||
package music
|
||||
|
||||
import music.Params.SAMPLES
|
||||
import music.filter.EaseFilter
|
||||
import music.filter.FilterQueue
|
||||
import music.generators.Generator
|
||||
import java.util.logging.Filter
|
||||
|
||||
|
||||
class Track {
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
class Bar(val beats: Int, val bpm: Int) {
|
||||
|
||||
val bps = bpm / 60f
|
||||
val tones = mutableSetOf<Beat>()
|
||||
|
||||
fun getSignal():Signal {
|
||||
var base = Signal((SAMPLES * beats / bps).toInt()) {0}
|
||||
for (tone in tones) {
|
||||
base = base.join((SAMPLES * tone.start / bps).toInt(), tone.getSignal(bps))
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class Beat(private val generator: Generator, private val duration: Float, val start: Float) {
|
||||
fun getSignal(bps: Float): Signal =
|
||||
generator.get(Samples(duration / bps, start / bps))
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package music.filter
|
||||
|
||||
import music.Signal
|
||||
|
||||
class Compressor(val threshold: Int, val ratio: Float, val outputGain: Float):Filter {
|
||||
|
||||
override fun stream(signal: Signal): Signal {
|
||||
return signal;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package music.filter
|
||||
|
||||
import music.Signal
|
||||
import music.map
|
||||
import music.div
|
||||
import music.times
|
||||
import java.util.*
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sign
|
||||
|
||||
class EchoFilter(delay: Int, private val volume: Float = 0.5f): Filter {
|
||||
private var cache: Queue<Int> = LinkedList(List(delay) {0})
|
||||
|
||||
override fun stream(signal: Signal): Signal =
|
||||
signal.map(this::apply)
|
||||
|
||||
private fun apply(value: Int): Int {
|
||||
val effect = value + cache.remove()
|
||||
cache.add(effect.toFloat().absoluteValue.pow(volume).toInt() * effect.sign)
|
||||
return effect;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package music.filter
|
||||
|
||||
import music.Signal
|
||||
|
||||
interface Filter {
|
||||
fun stream(signal: Signal): Signal
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
enum class EaseFilter(val duration: Int = 32): Filter {
|
||||
IN {
|
||||
override fun stream (signal: Signal): Signal {
|
||||
val len = kotlin.math.min(duration, signal.size)
|
||||
val start = signal.slice(0 until len)
|
||||
val end = signal.slice(len until signal.size)
|
||||
|
||||
return (start.withIndex().map { i -> (i.value * (i.index / len.toFloat())).toInt() } + end).toTypedArray();
|
||||
}
|
||||
},
|
||||
|
||||
OUT {
|
||||
override fun stream(signal: Signal): Signal {
|
||||
val len = kotlin.math.min(duration, signal.size)
|
||||
val sep = signal.size - len - 1;
|
||||
val start = signal.slice(0 until sep)
|
||||
val end = signal.slice(sep until signal.size)
|
||||
|
||||
return (start + end.withIndex().map { i -> (i.value * ((len - i.index) / len.toFloat())).toInt() }).toTypedArray();
|
||||
}
|
||||
},
|
||||
|
||||
INOUT {
|
||||
override fun stream(signal: Signal): Signal {
|
||||
val len = kotlin.math.min(duration, signal.size / 2)
|
||||
val sep = signal.size - len - 1;
|
||||
val start = signal.slice(0 until len)
|
||||
val mid = signal.slice(len until sep)
|
||||
val end = signal.slice(sep until signal.size)
|
||||
|
||||
return (start.withIndex().map { i -> (i.value * (i.index / len.toFloat())).toInt() }
|
||||
+ mid
|
||||
+ end.withIndex().map { i -> (i.value * ((len - i.index) / len.toFloat())).toInt() }
|
||||
).toTypedArray();
|
||||
}
|
||||
};
|
||||
|
||||
override fun stream(signal: Signal): Signal = signal
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package music.filter
|
||||
|
||||
import music.Signal
|
||||
|
||||
class FilterQueue(private vararg val filters: Filter): Filter {
|
||||
override fun stream(signal: Signal): Signal =
|
||||
filters.fold(signal, {acc, filter -> filter.stream(acc)})
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package music.filter
|
||||
|
||||
import music.Params.MAX_VAL
|
||||
import music.Signal
|
||||
import music.map
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class Limiter(val threshold: Float): Filter {
|
||||
val maxVal = (MAX_VAL * threshold).toInt()
|
||||
|
||||
override fun stream(signal: Signal): Signal =
|
||||
signal.map { max(min(it, maxVal), - maxVal) }
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package music.generators
|
||||
|
||||
import music.*
|
||||
import music.Params.SAMPLES
|
||||
import music.filter.EchoFilter
|
||||
import music.filter.FilterQueue
|
||||
import kotlin.math.max
|
||||
|
||||
class Drum(volume: Float, tone: Int = -36): Generator{
|
||||
val kickLen = (SAMPLES * 0.08).toInt();
|
||||
val filter = FilterQueue(EchoFilter(kickLen, .7f));
|
||||
|
||||
val gen1 = EasedToneGenerator(volume, tone, WaveformTransformation.SINE)
|
||||
val gen2 = EasedToneGenerator(volume / 2, tone + 12, WaveformTransformation.SINE)
|
||||
|
||||
override fun get(samples: Samples): Signal {
|
||||
val kick1 = gen1.get(samples.sliceArray(0 until kickLen))
|
||||
val kick2 = gen2.get(samples.sliceArray(0 until (kickLen / 2)))
|
||||
|
||||
return filter.stream(kick1.join(0, kick2) + Signal(max(samples.size - kickLen, 0)) {0}) / 1.5f;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package music.filter
|
||||
|
||||
import music.Samples
|
||||
import music.WaveformTransformation
|
||||
import music.generators.ToneGenerator
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class EaseFilterTest {
|
||||
@Test
|
||||
fun testOutputLength() {
|
||||
val gen = ToneGenerator(1f, 0, WaveformTransformation.SINE);
|
||||
val samples = Samples(1)
|
||||
val signal = gen.get(samples)
|
||||
|
||||
|
||||
assertEquals(samples.size, signal.size, "Generated samples should have same size as input")
|
||||
|
||||
for (filter in EaseFilter.values()) {
|
||||
assertEquals(samples.size, filter.stream(signal).size, "Filter $filter should not increase length")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue