some reworks

master
Anton Lydike 4 years ago
parent e127450404
commit a38f0b35d6

@ -0,0 +1,8 @@
package music
class Frac(val upper: Int, val lower: Int) {
val numerator = upper;
val denominator = lower;
fun value(): Double = upper / lower.toDouble();
}

@ -28,7 +28,7 @@ fun main() {
//val gen = ToneGenerator(.2f, -24, WaveformTransformation.SAWTOOTH)
val bpm = 60;
val bpm = 90;
val volume = 0.1f;
val tetrisSound = { volume: Float, tone: Int -> EasedToneGenerator(volume, tone, WaveformTransformation.SQUARE) }
@ -116,10 +116,6 @@ fun main() {
.pause(0.25) // 7
.add("A", 0.5, offset = -1)
.applyTo(bar2)
// D F A G F E
// C E D C B
// B C D E C A A
val echo = EchoFilter(SAMPLES / 12, .9f)
val limiter = Limiter(.9f);

@ -3,37 +3,37 @@ package music
import music.Params.SAMPLES
import kotlin.math.absoluteValue
typealias Samples = Array<Float>
typealias TimeSamples = Array<Float>
fun Samples(len: Float, start: Float = 0f): Samples =
Samples((len * SAMPLES).toInt()) { i -> start + (i / SAMPLES.toFloat())}
fun Samples(len: Float, start: Float = 0f): TimeSamples =
TimeSamples((len * SAMPLES).toInt()) { i -> start + (i / SAMPLES.toFloat())}
fun Samples(len: Int, start: Float = 0f): Samples = Samples(len.toFloat(), start)
fun Samples(len: Int, start: Float = 0f): TimeSamples = Samples(len.toFloat(), start)
fun Samples.map(mapper: (Float) -> (Float)): Samples =
fun TimeSamples.map(mapper: (Float) -> (Float)): TimeSamples =
Array(size) { mapper(this[it]) }
fun Samples.ceil(): Samples =
fun TimeSamples.ceil(): TimeSamples =
map { kotlin.math.ceil(it) }
fun Samples.sin(): Samples =
fun TimeSamples.sin(): TimeSamples =
map { kotlin.math.sin(it) }
fun Samples.mod(num: Int): Samples =
fun TimeSamples.mod(num: Int): TimeSamples =
map { it % num }
fun Samples.abs(): Samples =
fun TimeSamples.abs(): TimeSamples =
map { it.absoluteValue }
operator fun Samples.times(scalar: Float): Samples =
operator fun TimeSamples.times(scalar: Float): TimeSamples =
if (scalar == 1f) this else map { it * scalar }
operator fun Samples.plus(value: Float): Samples =
operator fun TimeSamples.plus(value: Float): TimeSamples =
map { it + value }
operator fun Samples.minus(value: Float): Samples =
operator fun TimeSamples.minus(value: Float): TimeSamples =
map { it - value }
operator fun Samples.div(value: Float): Samples =
operator fun TimeSamples.div(value: Float): TimeSamples =
map { it / value }

@ -6,53 +6,53 @@ import music.Params.PI
/**
* A Signal is string of Integer values which describe a curve, they go from - MAX_VAL to MAX_VAL
*/
typealias Signal = Array<Int>
typealias SoundSamples = Array<Int>
fun Signal.toBytes(): ByteArray =
fun SoundSamples.toBytes(): ByteArray =
Array(size * 2) { i -> (this[i/2] shr ((i % 2) * 8)).toByte() }.toByteArray()
fun Signal(samples: Samples): Signal =
Signal(samples.size) {i -> (samples[i] * MAX_VAL).toInt() }
fun SoundSamples(timeSamples: TimeSamples): SoundSamples =
SoundSamples(timeSamples.size) { i -> (timeSamples[i] * MAX_VAL).toInt() }
enum class WaveformTransformation {
SINE {
override fun apply(samples: Samples, volume: Float): Signal
= Signal(((samples * (2f * PI)).sin()) * volume)
override fun apply(timeSamples: TimeSamples, volume: Float): SoundSamples
= SoundSamples(((timeSamples * (2f * PI)).sin()) * volume)
},
SQUARE {
override fun apply(samples: Samples, volume: Float): Signal
= Signal(samples.times(2f).ceil().mod(2).times(2f).minus(1f) * (volume))
override fun apply(timeSamples: TimeSamples, volume: Float): SoundSamples
= SoundSamples(timeSamples.times(2f).ceil().mod(2).times(2f).minus(1f) * (volume))
},
SAWTOOTH {
override fun apply(samples: Samples, volume: Float): Signal
= Signal(((samples * 2f).mod(2) - 1f) * volume)
override fun apply(timeSamples: TimeSamples, volume: Float): SoundSamples
= SoundSamples(((timeSamples * 2f).mod(2) - 1f) * volume)
},
TRIANGLE {
override fun apply(samples: Samples, volume: Float): Signal
= Signal(samples.times(4f).mod(4).minus(2f).abs().minus(1f) * (volume))
override fun apply(timeSamples: TimeSamples, volume: Float): SoundSamples
= SoundSamples(timeSamples.times(4f).mod(4).minus(2f).abs().minus(1f) * (volume))
};
open fun apply(samples: Samples, volume: Float): Signal = Signal(samples * volume)
open fun apply(timeSamples: TimeSamples, volume: Float): SoundSamples = SoundSamples(timeSamples * volume)
}
fun Signal.map(mapper: (Int) -> (Int)): Signal =
fun SoundSamples.map(mapper: (Int) -> (Int)): SoundSamples =
Array(size) { mapper(this[it]) }
operator fun Signal.times(scalar: Float): Signal =
operator fun SoundSamples.times(scalar: Float): SoundSamples =
if (scalar == 1f) this else map { (it * scalar).toInt() }
operator fun Signal.plus(value: Int): Signal =
operator fun SoundSamples.plus(value: Int): SoundSamples =
map { it + value }
operator fun Signal.minus(value: Int): Signal =
operator fun SoundSamples.minus(value: Int): SoundSamples =
map { it - value }
operator fun Signal.div(value: Float): Signal =
operator fun SoundSamples.div(value: Float): SoundSamples =
map { (it / value).toInt() }
fun Signal.join(start: Int, signal: Signal): Signal
fun SoundSamples.join(start: Int, soundSamples: SoundSamples): SoundSamples
= (this.sliceArray(0 until start)
+ signal.withIndex().map { i -> this.elementAtOrElse(i.index + start) {0} + i.value }
+ this.sliceArray(start + signal.size until this.size))
+ soundSamples.withIndex().map { i -> this.elementAtOrElse(i.index + start) {0} + i.value }
+ this.sliceArray(start + soundSamples.size until this.size))

@ -1,10 +1,7 @@
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 {
@ -19,8 +16,8 @@ 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}
fun getSignal():SoundSamples {
var base = SoundSamples((SAMPLES * beats / bps).toInt()) {0}
for (tone in tones) {
base = base.join((SAMPLES * tone.start / bps).toInt(), tone.getSignal(bps))
}
@ -30,6 +27,6 @@ class Bar(val beats: Int, val bpm: Int) {
}
class Beat(private val generator: Generator, private val duration: Float, val start: Float) {
fun getSignal(bps: Float): Signal =
fun getSignal(bps: Float): SoundSamples =
generator.get(Samples(duration / bps, start / bps))
}

@ -1,10 +1,29 @@
package music.filter
import music.Signal
import music.Frac
import music.SoundSamples
import music.map
import kotlin.math.log10
class Compressor(val threshold: Int, val ratio: Float, val outputGain: Float):Filter {
/**
* @param threshold the threshold when the compressor starts in decibel
* @param ratio how much to compress when crossing the threshold. A ration of 6/1 means 1 decibel output for each 6 decibel over the threshold
* @param outputGain how much the signal should be amplified after compression
*/
class Compressor(private val threshold: Int, val ratio: Frac, val outputGain: Float):Filter() {
override fun stream(soundSamples: SoundSamples): SoundSamples {
return soundSamples.map(this::apply)
}
private fun apply(sample: Int): Int {
val db = signalToDB(sample)
if (db > threshold) {
val surplus = db - threshold;
val reduction = surplus - (surplus / ratio.value())
return dBToSignal(db - reduction + outputGain)
}
override fun stream(signal: Signal): Signal {
return signal;
return dBToSignal(db + outputGain);
}
}

@ -1,9 +1,7 @@
package music.filter
import music.Signal
import music.SoundSamples
import music.map
import music.div
import music.times
import java.util.*
import kotlin.math.absoluteValue
import kotlin.math.pow
@ -12,8 +10,8 @@ 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)
override fun stream(soundSamples: SoundSamples): SoundSamples =
soundSamples.map(this::apply)
private fun apply(value: Int): Int {
val effect = value + cache.remove()

@ -1,53 +1,40 @@
package music.filter
import music.Signal
interface Filter {
fun stream(signal: Signal): Signal
}
import music.SoundSamples
import kotlin.math.log10
import kotlin.math.pow
abstract class Filter {
abstract fun stream(soundSamples: SoundSamples): SoundSamples
protected fun signalToDB(signal: Int): Double =
20 * log10(signal.toDouble())
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)
protected fun dBToSignal(db: Double): Int =
10.0.pow(db / 20).toInt()
}
return (start.withIndex().map { i -> (i.value * (i.index / len.toFloat())).toInt() } + end).toTypedArray();
}
},
object DefaultEases {
val IN = EaseFilter(32, 0)
val OUT = EaseFilter(0, 32)
val INOUT = EaseFilter(32, 32)
}
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)
class EaseFilter(val inDuration: Int, val outDuration: Int): Filter() {
return (start + end.withIndex().map { i -> (i.value * ((len - i.index) / len.toFloat())).toInt() }).toTypedArray();
}
},
override fun stream(soundSamples: SoundSamples): SoundSamples {
val inlen = kotlin.math.min(inDuration, soundSamples.size - outDuration)
val outlen = kotlin.math.min(outDuration, soundSamples.size - inlen)
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)
val sep = soundSamples.size - outlen - 1;
val start = soundSamples.slice(0 until inlen)
val mid = soundSamples.slice(inlen until sep)
val end = soundSamples.slice(sep until soundSamples.size)
return (start.withIndex().map { i -> (i.value * (i.index / len.toFloat())).toInt() }
return (start.withIndex().map { i -> (i.value * (i.index / inlen.toFloat())).toInt() }
+ mid
+ end.withIndex().map { i -> (i.value * ((len - i.index) / len.toFloat())).toInt() }
+ end.withIndex().map { i -> (i.value * ((outlen - i.index) / outlen.toFloat())).toInt() }
).toTypedArray();
}
};
override fun stream(signal: Signal): Signal = signal
}

@ -1,8 +1,8 @@
package music.filter
import music.Signal
import music.SoundSamples
class FilterQueue(private vararg val filters: Filter): Filter {
override fun stream(signal: Signal): Signal =
filters.fold(signal, {acc, filter -> filter.stream(acc)})
override fun stream(soundSamples: SoundSamples): SoundSamples =
filters.fold(soundSamples, { acc, filter -> filter.stream(acc)})
}

@ -1,7 +1,7 @@
package music.filter
import music.Params.MAX_VAL
import music.Signal
import music.SoundSamples
import music.map
import kotlin.math.max
import kotlin.math.min
@ -9,6 +9,6 @@ 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) }
override fun stream(soundSamples: SoundSamples): SoundSamples =
soundSamples.map { max(min(it, maxVal), - maxVal) }
}

@ -13,10 +13,10 @@ class Drum(volume: Float, tone: Int = -36): Generator{
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)))
override fun get(timeSamples: TimeSamples): SoundSamples {
val kick1 = gen1.get(timeSamples.sliceArray(0 until kickLen))
val kick2 = gen2.get(timeSamples.sliceArray(0 until (kickLen / 2)))
return filter.stream(kick1.join(0, kick2) + Signal(max(samples.size - kickLen, 0)) {0}) / 1.5f;
return filter.stream(kick1.join(0, kick2) + SoundSamples(max(timeSamples.size - kickLen, 0)) {0}) / 1.5f;
}
}

@ -1,11 +1,10 @@
package music.generators
import music.Samples
import music.Signal
import music.WaveformTransformation
import music.TimeSamples
import music.SoundSamples
interface Generator {
fun get(samples: Samples): Signal
fun get(timeSamples: TimeSamples): SoundSamples
}

@ -1,22 +1,18 @@
package music.generators
import music.Samples
import music.Signal
import music.TimeSamples
import music.SoundSamples
import music.WaveformTransformation
import music.filter.EaseFilter
import music.times
import kotlin.math.pow
import kotlin.math.sign
open class ToneGenerator(private val volume: Float, private val tone: Int, private val waveform: WaveformTransformation) : Generator {
override fun get(samples: Samples): Signal {
return applyEffects(waveform.apply(generate(samples), volume))
}
protected open fun applyEffects(signal: Signal): Signal = signal
override fun get(timeSamples: TimeSamples): SoundSamples =
applyEffects(waveform.apply(timeSamples * getFreqOfTone(tone), volume))
protected open fun generate(samples: Samples): Samples =
samples * getFreqOfTone(tone)
protected open fun applyEffects(soundSamples: SoundSamples): SoundSamples = soundSamples
protected fun getFreqOfTone(tone: Float): Float =
F0 * 2f.pow(tone / 12f)
@ -30,6 +26,6 @@ open class ToneGenerator(private val volume: Float, private val tone: Int, priva
}
class EasedToneGenerator(volume: Float, tone: Int, waveform: WaveformTransformation) : ToneGenerator(volume, tone, waveform) {
override fun applyEffects(signal: Signal): Signal =
EaseFilter.INOUT.stream(signal)
override fun applyEffects(soundSamples: SoundSamples): SoundSamples =
EaseFilter.INOUT.stream(soundSamples)
}
Loading…
Cancel
Save