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

@ -3,37 +3,37 @@ package music
import music.Params.SAMPLES import music.Params.SAMPLES
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
typealias Samples = Array<Float> typealias TimeSamples = Array<Float>
fun Samples(len: Float, start: Float = 0f): Samples = fun Samples(len: Float, start: Float = 0f): TimeSamples =
Samples((len * SAMPLES).toInt()) { i -> start + (i / SAMPLES.toFloat())} 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]) } Array(size) { mapper(this[it]) }
fun Samples.ceil(): Samples = fun TimeSamples.ceil(): TimeSamples =
map { kotlin.math.ceil(it) } map { kotlin.math.ceil(it) }
fun Samples.sin(): Samples = fun TimeSamples.sin(): TimeSamples =
map { kotlin.math.sin(it) } map { kotlin.math.sin(it) }
fun Samples.mod(num: Int): Samples = fun TimeSamples.mod(num: Int): TimeSamples =
map { it % num } map { it % num }
fun Samples.abs(): Samples = fun TimeSamples.abs(): TimeSamples =
map { it.absoluteValue } 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 } 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 } map { it + value }
operator fun Samples.minus(value: Float): Samples = operator fun TimeSamples.minus(value: Float): TimeSamples =
map { it - value } map { it - value }
operator fun Samples.div(value: Float): Samples = operator fun TimeSamples.div(value: Float): TimeSamples =
map { it / value } 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 * 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() Array(size * 2) { i -> (this[i/2] shr ((i % 2) * 8)).toByte() }.toByteArray()
fun Signal(samples: Samples): Signal = fun SoundSamples(timeSamples: TimeSamples): SoundSamples =
Signal(samples.size) {i -> (samples[i] * MAX_VAL).toInt() } SoundSamples(timeSamples.size) { i -> (timeSamples[i] * MAX_VAL).toInt() }
enum class WaveformTransformation { enum class WaveformTransformation {
SINE { SINE {
override fun apply(samples: Samples, volume: Float): Signal override fun apply(timeSamples: TimeSamples, volume: Float): SoundSamples
= Signal(((samples * (2f * PI)).sin()) * volume) = SoundSamples(((timeSamples * (2f * PI)).sin()) * volume)
}, },
SQUARE { SQUARE {
override fun apply(samples: Samples, volume: Float): Signal override fun apply(timeSamples: TimeSamples, volume: Float): SoundSamples
= Signal(samples.times(2f).ceil().mod(2).times(2f).minus(1f) * (volume)) = SoundSamples(timeSamples.times(2f).ceil().mod(2).times(2f).minus(1f) * (volume))
}, },
SAWTOOTH { SAWTOOTH {
override fun apply(samples: Samples, volume: Float): Signal override fun apply(timeSamples: TimeSamples, volume: Float): SoundSamples
= Signal(((samples * 2f).mod(2) - 1f) * volume) = SoundSamples(((timeSamples * 2f).mod(2) - 1f) * volume)
}, },
TRIANGLE { TRIANGLE {
override fun apply(samples: Samples, volume: Float): Signal override fun apply(timeSamples: TimeSamples, volume: Float): SoundSamples
= Signal(samples.times(4f).mod(4).minus(2f).abs().minus(1f) * (volume)) = 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]) } 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() } 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 } map { it + value }
operator fun Signal.minus(value: Int): Signal = operator fun SoundSamples.minus(value: Int): SoundSamples =
map { it - value } map { it - value }
operator fun Signal.div(value: Float): Signal = operator fun SoundSamples.div(value: Float): SoundSamples =
map { (it / value).toInt() } 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) = (this.sliceArray(0 until start)
+ signal.withIndex().map { i -> this.elementAtOrElse(i.index + start) {0} + i.value } + soundSamples.withIndex().map { i -> this.elementAtOrElse(i.index + start) {0} + i.value }
+ this.sliceArray(start + signal.size until this.size)) + this.sliceArray(start + soundSamples.size until this.size))

@ -1,10 +1,7 @@
package music package music
import music.Params.SAMPLES import music.Params.SAMPLES
import music.filter.EaseFilter
import music.filter.FilterQueue
import music.generators.Generator import music.generators.Generator
import java.util.logging.Filter
class Track { class Track {
@ -19,8 +16,8 @@ class Bar(val beats: Int, val bpm: Int) {
val bps = bpm / 60f val bps = bpm / 60f
val tones = mutableSetOf<Beat>() val tones = mutableSetOf<Beat>()
fun getSignal():Signal { fun getSignal():SoundSamples {
var base = Signal((SAMPLES * beats / bps).toInt()) {0} var base = SoundSamples((SAMPLES * beats / bps).toInt()) {0}
for (tone in tones) { for (tone in tones) {
base = base.join((SAMPLES * tone.start / bps).toInt(), tone.getSignal(bps)) 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) { 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)) generator.get(Samples(duration / bps, start / bps))
} }

@ -1,10 +1,29 @@
package music.filter 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 dBToSignal(db + outputGain);
return signal;
} }
} }

@ -1,9 +1,7 @@
package music.filter package music.filter
import music.Signal import music.SoundSamples
import music.map import music.map
import music.div
import music.times
import java.util.* import java.util.*
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.pow import kotlin.math.pow
@ -12,8 +10,8 @@ import kotlin.math.sign
class EchoFilter(delay: Int, private val volume: Float = 0.5f): Filter { class EchoFilter(delay: Int, private val volume: Float = 0.5f): Filter {
private var cache: Queue<Int> = LinkedList(List(delay) {0}) private var cache: Queue<Int> = LinkedList(List(delay) {0})
override fun stream(signal: Signal): Signal = override fun stream(soundSamples: SoundSamples): SoundSamples =
signal.map(this::apply) soundSamples.map(this::apply)
private fun apply(value: Int): Int { private fun apply(value: Int): Int {
val effect = value + cache.remove() val effect = value + cache.remove()

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

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

@ -1,7 +1,7 @@
package music.filter package music.filter
import music.Params.MAX_VAL import music.Params.MAX_VAL
import music.Signal import music.SoundSamples
import music.map import music.map
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -9,6 +9,6 @@ import kotlin.math.min
class Limiter(val threshold: Float): Filter { class Limiter(val threshold: Float): Filter {
val maxVal = (MAX_VAL * threshold).toInt() val maxVal = (MAX_VAL * threshold).toInt()
override fun stream(signal: Signal): Signal = override fun stream(soundSamples: SoundSamples): SoundSamples =
signal.map { max(min(it, maxVal), - maxVal) } 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 gen1 = EasedToneGenerator(volume, tone, WaveformTransformation.SINE)
val gen2 = EasedToneGenerator(volume / 2, tone + 12, WaveformTransformation.SINE) val gen2 = EasedToneGenerator(volume / 2, tone + 12, WaveformTransformation.SINE)
override fun get(samples: Samples): Signal { override fun get(timeSamples: TimeSamples): SoundSamples {
val kick1 = gen1.get(samples.sliceArray(0 until kickLen)) val kick1 = gen1.get(timeSamples.sliceArray(0 until kickLen))
val kick2 = gen2.get(samples.sliceArray(0 until (kickLen / 2))) 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 package music.generators
import music.Samples import music.TimeSamples
import music.Signal import music.SoundSamples
import music.WaveformTransformation
interface Generator { interface Generator {
fun get(samples: Samples): Signal fun get(timeSamples: TimeSamples): SoundSamples
} }

@ -1,22 +1,18 @@
package music.generators package music.generators
import music.Samples import music.TimeSamples
import music.Signal import music.SoundSamples
import music.WaveformTransformation import music.WaveformTransformation
import music.filter.EaseFilter import music.filter.EaseFilter
import music.times import music.times
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sign
open class ToneGenerator(private val volume: Float, private val tone: Int, private val waveform: WaveformTransformation) : Generator { 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 = protected open fun applyEffects(soundSamples: SoundSamples): SoundSamples = soundSamples
samples * getFreqOfTone(tone)
protected fun getFreqOfTone(tone: Float): Float = protected fun getFreqOfTone(tone: Float): Float =
F0 * 2f.pow(tone / 12f) 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) { class EasedToneGenerator(volume: Float, tone: Int, waveform: WaveformTransformation) : ToneGenerator(volume, tone, waveform) {
override fun applyEffects(signal: Signal): Signal = override fun applyEffects(soundSamples: SoundSamples): SoundSamples =
EaseFilter.INOUT.stream(signal) EaseFilter.INOUT.stream(soundSamples)
} }
Loading…
Cancel
Save