Я не думаю, что SoundCloud делает что-то особенное. На их первой странице есть много песен, которые очень плоские. Это больше связано с тем, как воспринимается деталь и какова общая динамика песни. Основное различие заключается в том, что SoundCloud рисует абсолютное значение. (Отрицательная сторона изображения просто зеркало.)
Для демонстрации, вот основной белый шум участка с прямыми линиями:
Теперь, как правило, наполнитель используется, чтобы сделать общий контур легче увидеть. Это уже много делает для появления:
увеличенных осциллограмм («Уменьшенном», в частности), как правило, используют зеркальный эффект, поскольку динамика становится более заметным:
Бары являются еще одним способом визуализации и могут дать иллюзию детали:
Псевдо процедура для типичной формы волны графика (в среднем абс и зеркала) может выглядеть следующим образом:
for (each pixel in width of image) {
var sum = 0
for (each sample in subset contained within pixel) {
sum = sum + abs(sample)
}
var avg = sum/length of subset
draw line(avg to -avg)
}
Это эффективно, как сжатие оси времени, как RMS окна. (RMS также можно использовать, но они почти одинаковы.) Теперь форма волны показывает общую динамику.
Это не слишком отличается от того, что вы уже делаете, просто абс, зеркало и заливку. Для ящиков, таких как SoundCloud, вы будете рисовать прямоугольники.
Как раз в качестве бонуса, здесь MCVE написан на Java для генерации формы волны с блоками, как описано. (Извините, если Java не является вашим языком.) Фактический код чертежа находится в верхней части. Эта программа также нормализуется, то есть форма волны «растягивается» до высоты изображения.
Этот простой вывод такой же, как выше псевдо подпрограммы:
Этот выход с коробками очень похож на SoundCloud:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.sound.sampled.*;
public class BoxWaveform {
static int boxWidth = 4;
static Dimension size = new Dimension(boxWidth == 1 ? 512 : 513, 97);
static BufferedImage img;
static JPanel view;
// draw the image
static void drawImage(float[] samples) {
Graphics2D g2d = img.createGraphics();
int numSubsets = size.width/boxWidth;
int subsetLength = samples.length/numSubsets;
float[] subsets = new float[numSubsets];
// find average(abs) of each box subset
int s = 0;
for(int i = 0; i < subsets.length; i++) {
double sum = 0;
for(int k = 0; k < subsetLength; k++) {
sum += Math.abs(samples[s++]);
}
subsets[i] = (float)(sum/subsetLength);
}
// find the peak so the waveform can be normalized
// to the height of the image
float normal = 0;
for(float sample : subsets) {
if(sample > normal)
normal = sample;
}
// normalize and scale
normal = 32768.0f/normal;
for(int i = 0; i < subsets.length; i++) {
subsets[i] *= normal;
subsets[i] = (subsets[i]/32768.0f) * (size.height/2);
}
g2d.setColor(Color.GRAY);
// convert to image coords and do actual drawing
for(int i = 0; i < subsets.length; i++) {
int sample = (int)subsets[i];
int posY = (size.height/2) - sample;
int negY = (size.height/2) + sample;
int x = i * boxWidth;
if(boxWidth == 1) {
g2d.drawLine(x, posY, x, negY);
} else {
g2d.setColor(Color.GRAY);
g2d.fillRect(x + 1, posY + 1, boxWidth - 1, negY - posY - 1);
g2d.setColor(Color.DARK_GRAY);
g2d.drawRect(x, posY, boxWidth, negY - posY);
}
}
g2d.dispose();
view.repaint();
view.requestFocus();
}
// handle most WAV and AIFF files
static void loadImage() {
JFileChooser chooser = new JFileChooser();
int val = chooser.showOpenDialog(null);
if(val != JFileChooser.APPROVE_OPTION) {
return;
}
File file = chooser.getSelectedFile();
float[] samples;
try {
AudioInputStream in = AudioSystem.getAudioInputStream(file);
AudioFormat fmt = in.getFormat();
if(fmt.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) {
throw new UnsupportedAudioFileException("unsigned");
}
boolean big = fmt.isBigEndian();
int chans = fmt.getChannels();
int bits = fmt.getSampleSizeInBits();
int bytes = bits + 7 >> 3;
int frameLength = (int)in.getFrameLength();
int bufferLength = chans * bytes * 1024;
samples = new float[frameLength];
byte[] buf = new byte[bufferLength];
int i = 0;
int bRead;
while((bRead = in.read(buf)) > -1) {
for(int b = 0; b < bRead;) {
double sum = 0;
// (sums to mono if multiple channels)
for(int c = 0; c < chans; c++) {
if(bytes == 1) {
sum += buf[b++] << 8;
} else {
int sample = 0;
// (quantizes to 16-bit)
if(big) {
sample |= (buf[b++] & 0xFF) << 8;
sample |= (buf[b++] & 0xFF);
b += bytes - 2;
} else {
b += bytes - 2;
sample |= (buf[b++] & 0xFF);
sample |= (buf[b++] & 0xFF) << 8;
}
final int sign = 1 << 15;
final int mask = -1 << 16;
if((sample & sign) == sign) {
sample |= mask;
}
sum += sample;
}
}
samples[i++] = (float)(sum/chans);
}
}
} catch(Exception e) {
problem(e);
return;
}
if(img == null) {
img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
}
drawImage(samples);
}
static void problem(Object msg) {
JOptionPane.showMessageDialog(null, String.valueOf(msg));
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Box Waveform");
JPanel content = new JPanel(new BorderLayout());
frame.setContentPane(content);
JButton load = new JButton("Load");
load.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
loadImage();
}
});
view = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(img != null) {
g.drawImage(img, 1, 1, img.getWidth(), img.getHeight(), null);
}
}
};
view.setBackground(Color.WHITE);
view.setPreferredSize(new Dimension(size.width + 2, size.height + 2));
content.add(view, BorderLayout.CENTER);
content.add(load, BorderLayout.SOUTH);
frame.pack();
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Примечание: для для простоты эта программа загружает весь аудиофайл в память. Некоторые JVM могут бросать OutOfMemoryError
. Чтобы исправить это, запустите с увеличенным размером кучи as described here.
Вы пробовали вертикальную шкалу «обратного журнала»? Это будет подчеркивать вариацию. Также вы можете попробовать средние значения вместо avg и/или отрезать образцы верхнего и нижнего отсечения – radai
@radai Я не думал о том, чтобы использовать средства, я попробую. Какой обратный журнал вы имеете в виду? Это будет экспоненциальная функция? – Jawap
Ваш входной диапазон известен - от 0 до максимального объема (в зависимости от глубины образца?). Вам нужна шкала, которая помещает все низкие образцы близко к основанию, но оставляет много места для больших значений. Скажем val^2, деленное на max_value^2 – radai