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
|
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 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