В прошлый раз мы научились работать с WAV-файлами . Точнее, мы с вами научились работать с заголовком, а сами данные за нас проиграл кто-то другой. Настало время поработать непосредственно с самими данными, а заодно и попрактиковаться в работе с двухмерной графикой при помощи Java 2D.

Как уже отмечалось, сэмплы в WAV файле идут вперемешку для всех каналов, а также могут быть закодированы по крайней мере двумя разными способами. Не факт, что нас интересуют все каналы, которые есть в файле, и уж совершенно точно нам не хотелось бы думать о разных способах кодирования. Намного удобнее было бы работать с каналами по отдельности, и чтобы сэмплы всегда представлялись, скажем, действительным числом от -1 до 1. Сказано — сделано:

type ChannelData = Array [ Double ]
type Channels = Array [ ChannelData ]

def rawDataToChannels ( wavInfo : WavInfo,
rawData : Array [ Byte ] ) : Channels = {
val samplesNumber = ( wavInfo. dataSize / wavInfo. blockSize ) . toInt
val wavChannels : Channels = {
(
for ( _ < — 1L to wavInfo. channels )
yield Array. ofDim [ Double ] ( samplesNumber )
) . toArray
}
val sampleSizeInBytes = ( wavInfo. blockSize / wavInfo. channels ) . toInt
val maxAmplitude = Math. pow ( 2 , 8 * sampleSizeInBytes — 1 )

for ( i < 0 until samplesNumber ) {
for ( ch < 0 until wavInfo. channels . toInt ) {
val from = ( i * wavInfo. blockSize +
ch * ( wavInfo. blockSize / wavInfo. channels )
) . toInt
val sampleLong = arraySliceToLong ( rawData, from,
from + sampleSizeInBytes )
val sampleDouble = { // (*)
if ( sampleSizeInBytes == 2 ) sampleLong. toShort . toLong
else ( Byte. MaxValue . toLong — sampleLong ) . toDouble
}
wavChannels ( ch ) ( i ) = sampleDouble / maxAmplitude
}
}

wavChannels
}

Приведенный код, как и в прошлой заметке, не претендует на эффективность. Обратите внимание на строчку с пометкой (*). О разном представлении чисел в 8-и битовых и 16-и битовых WAV говорилось в прошлый раз.

Теперь мы можем с легкостью визуализировать содержимое файла:

def processFile ( inputFile : String, outputFile : String,
channelNumber : Int, width : Int,
height : Int ) : Unit = {
var optWavFile : Option [ WavFile ] = None
try {
optWavFile = Some ( new WavFile ( inputFile ) )
optWavFile foreach { wavFile =>
val info = wavFile. info ( )
val rawData = wavFile. rawData ( )
val channels = rawDataToChannels ( info, rawData )

val img = new BufferedImage ( width, height,
BufferedImage. TYPE_INT_ARGB )
val graphics = img. createGraphics ( )

graphics. setColor ( new Color ( 255 , 255 , 255 ) )
graphics. fillRect ( 0 , 0 , width, height )

graphics. setColor ( new Color ( 0 , 0 , 255 ) )

val totalSamples = channels ( channelNumber ) . length

for ( x < 0 until width ) {
val fromSample = x * ( totalSamples / width )
val toSample = ( x + 1 ) * ( totalSamples / width )
var min = 0.0
var max = 0.0
for ( sn < — fromSample to toSample ) {
val sample = channels ( channelNumber ) ( sn )
min = Math. min ( min, sample )
max = Math. max ( max, sample )
}
// (0;0) point is top left corner
// be careful and don’t draw an image upside down!
graphics. draw ( new Line2D. Double (
x. toDouble , ( height/ 2 ) . toDouble — max * ( height/ 2 ) . toDouble ,
x. toDouble , ( height/ 2 ) . toDouble — min * ( height/ 2 ) . toDouble
) )
}

ImageIO. write ( img, «PNG» , new File ( outputFile ) )
}
} finally {
optWavFile. foreach ( _ . close ( ) )
}
}

Алгоритм, как видите, не сложный. Ширина картинки известна заранее. Для каждой координаты X определяется соответствующий интервал на дорожке. На этом интервале находятся максимальные и минимальные значения сэмплов, которые и отображаются для данного X.

В первой версии программы была забавная ошибка — картинка рисовалась вверх ногами, так как я забыл, что за начало координат в графических библиотеках обычно принято считать левый верхний угол. Эту ошибку, кстати, довольно сложно заметить, когда рисуешь waveform!

А вот и примеры вывода программы:

Вверху вы видите две картинки для стерео-файла, нарисованные для левой и правой его дорожки. И за ними чуть ниже нарисована дорожка для моно-файла, полученного путем объединения дорожек в стерео-файле. Если наложить друг на друга первую и вторую картинку, получится третья, что и ожидалось.

Исходники к этой заметке вы найдете в этом репозитории . На момент написания этих строк программа не была протестирована на 24-х битовых WAV-файлах, а также файлах, содержащих дополнительные секции помимо «data». Такие файлы, например, генерирует Reaper. Если хотите, можете считать доработку программы соответствующим образом своим домашним заданием.

Как видите, ничего сложного. Вооруженные этими знаниями, мы уже можем спокойно написать несложный аудио-редактор, способный не только вырезать-вставлять звуки, но и делать их громче-тише, быстрее-медленнее, объединять несколько дорожек в одну и даже прогонять фильтры вроде автоматического вырезания пауз, компрессора, лимитера и нормализатора. Остается, однако, открытым вопрос о том, как сделать подавление шумов или деэссинг.

admin

Share
Published by
admin

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

1 месяц ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

1 месяц ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

1 месяц ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

1 месяц ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

1 месяц ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

1 месяц ago