#### RemiAudio
#### Copyright (C) 2022-2024 Remilia Scarlet <remilia@posteo.jp>
#### Based on https://github.com/superctr/adpcm/blob/master/yma_codec.c
#### Copyright (C) 2019 Ian Karlsson
####
#### This program is free software: you can redistribute it and/or modify it
#### under the terms of the GNU Affero General Public License as published by
#### the Free Software Foundation, either version 3 of the License, or (at your
#### option) any later version.
####
#### This program is distributed in the hope that it will be useful, but WITHOUT
#### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
#### FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
#### License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program. If not, see <https://www.gnu.org/licenses/>.
####
#### YM2610 ADPCM-A Codec
####
module RemiAudio::Codecs
# A codec for YM2610 ADPCM-A samples.
class YM2610Codec
# :nodoc:
YM2610_STEP_TABLE = [ 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88,
97, 107,118,130,143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408,
449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552 ]
YM2610_STEP_ADJUST = [ -1, -1, -1, -1, 2, 5, 7, 9]
YM2610_DELTAS = [ 1, 3, 5, 7, 9, 11, 13, 15,
-1, -3, -5, -7, -9, -11, -13, -15 ]
@history : Int16 = 0
@stepHistory : UInt8 = 0
@nib : UInt8 = 0
@bufSamp : UInt8 = 0
@srcIdx : UInt8 = 0
private def reset
@history = 0
@stepHistory = 0u8
@nib = 0u8
@bufSamp = 0u8
end
private def doStep(step : UInt8) : Int16
stepSize : UInt16 = YM2610_STEP_TABLE[@stepHistory].to_u16!
delta : Int16 = (YM2610_DELTAS[step & 15] * stepSize.to_i16!).to_i16!.tdiv(8)
ret : Int16 = (@history + delta) & 0xfff
newStep : Int8 = 0
unless (ret & 0x800) == 0
ret |= ~0xfff
end
@history = ret
newStep = @stepHistory.to_i8! + YM2610_STEP_ADJUST[step & 7]
@stepHistory = if newStep > 48
48u8
elsif newStep < 0
0u8
else
newStep.to_u8!
end
ret
end
private def doEncStep(input : Int16) : UInt8
stepSize : UInt16 = YM2610_STEP_TABLE[@stepHistory].to_u16!
delta : Int16 = input - @history
samp : UInt8 = 0
bit : Int8 = 3
samp = 8 if delta < 0
delta = delta.abs
until bit == 0
bit -= 1
if delta >= stepSize
samp |= (1 << bit)
delta -= stepSize
end
stepSize = stepSize >> 1
end
doStep(samp)
samp
end
# Converts two signed 16-bit integer samples into one YM2610 ADPCM-A sample.
def int16ToAdpcm(sample1 : Int16, sample2 : Int16) : UInt8
ret : UInt8 = 0
step : UInt8 = 0
2.times do |i|
smp = (i == 0 ? sample1 : sample2)
smp += 8 if smp < 0x7ff8 # Round up
smp = smp >> 4
step = doEncStep(smp)
if @nib != 0
ret = @bufSamp | (step & 15)
@nib = 0
else
@bufSamp = (step & 15) << 4
@nib = 1
end
end
ret
end
# Converts an array of signed 16-bit integer samples into an array of YM2610
# ADPCM-A samples.
def int16ToAdpcm(samples : Array(Int16)) : Array(UInt8)
reset
samples.map { |samp| int16ToAdpcm(samp) }
end
# Convert a YM2610 ADPCM-A sample into two signed 16-bit integer samples.
def adpcmToInt16(sample : UInt8) : Tuple(Int16, Int16)
step : Int8 = 0
ret1 : Int16 = 0
ret2 : Int16 = 0
2.times do |i|
step = sample.to_i8! << @nib
step = step >> 4
if @nib == 0
@nib = 4
else
@nib = 0
end
if i == 0
ret1 = doStep(step.to_u8!) << 4
else
ret2 = doStep(step.to_u8!) << 4
end
end
{ret1, ret2}
end
# Converts an array of YM2610 ADPCM-A samples into an array of signed 16-bit
# integer samples.
def adpcmToInt16(samples : Array(UInt8)) : Array(Int16)
reset
samples.map { |samp| adpcmToInt16(samp) }
end
end
end