bsnes-plus/bsnes/ui-qt/tools/soundviewer.cpp

217 lines
6.1 KiB
C++

#include "soundviewer.moc"
SoundViewerWindow *soundViewerWindow;
#include <ctgmath>
#define OCTAVE_OFFSET 7
#define NUM_OCTAVES 8
#define KEY_HEIGHT 48
#define KEY_WIDTH 12
#define KEY_BLACK_MARGIN 2
#define METER_HEIGHT 12
#define METER_MARGIN 2
const QRgb colors[] = {
0xFFFF0000,
0xFFFFB200,
0xFF99FF00,
0xFF00FF19,
0xFF00CBFF,
0xFF007FFF,
0xFF3300FF,
0xFFE500FF
};
SoundViewerWidget::SoundViewerWidget(unsigned ch) {
this->channel = ch;
this->volume = 0.0;
this->panL = 0.0;
this->panR = 0.0;
this->note = -1;
initPixmap();
setMinimumWidth(NUM_OCTAVES * pixmap.width());
setMaximumWidth(NUM_OCTAVES * pixmap.width());
setMinimumHeight(pixmap.height() + METER_HEIGHT);
setMaximumHeight(pixmap.height() + METER_HEIGHT);
}
void SoundViewerWidget::initPixmap() {
// create the pixmap for a single octave on the keyboard
pixmap = QPixmap(7 * KEY_WIDTH, KEY_HEIGHT);
QPainter painter(&pixmap);
painter.fillRect(pixmap.rect(), Qt::white);
painter.setPen(Qt::black);
// white keys
#define key(num, pos) \
keyRect[num] = QRect(pos * KEY_WIDTH, 0, KEY_WIDTH - 1, KEY_HEIGHT - 1);\
painter.drawRect(keyRect[num]); \
keyRect[num].adjust(1, 1, 0, 0);
key(0, 0); key(2, 1); key(4, 2); key(5 ,3); key(7, 4); key(9, 5); key(11, 6);
#undef key
// black keys
#define key(num, pos)\
keyRect[num] = QRect(pos * KEY_WIDTH + (0.5 * KEY_WIDTH + KEY_BLACK_MARGIN), \
0, KEY_WIDTH - 2 * KEY_BLACK_MARGIN, 0.6 * KEY_HEIGHT); \
painter.fillRect(keyRect[num], Qt::black);
key(1, 0); key(3, 1); key(6, 3); key(8, 4); key(10, 5);
#undef key
}
void SoundViewerWidget::refresh() {
// get volume/panning
unsigned outx = abs((int8_t)SNES::dsp.read(0x9 /* SPC_DSP::v_outx */ + (this->channel << 4)));
// this is a pretty crappy way to meter volume, but it mostly works
this->volume = (((double)outx / 127) + 3 * this->volume) / 4;
unsigned mvol_l = abs((int8_t)SNES::dsp.read(0x0c));
unsigned mvol_r = abs((int8_t)SNES::dsp.read(0x1c));
unsigned vvol_l = abs((int8_t)SNES::dsp.read(0x0 + (this->channel << 4)));
unsigned vvol_r = abs((int8_t)SNES::dsp.read(0x1 + (this->channel << 4)));
// voice volume is scaled down less than master volume since it usually seems to be set pretty low
this->panL = (double)(mvol_l * vvol_l) / (128 * 32);
this->panR = (double)(mvol_r * vvol_r) / (128 * 32);
// get pitch/note
if (this->volume > 0.01) {
unsigned pitch = ((SNES::dsp.read(0x2 /* SPC_DSP::v_pitchl */ + (this->channel << 4)) << 0)
+ (SNES::dsp.read(0x3 /* SPC_DSP::v_pitchh */ + (this->channel << 4)) << 8));
double l = log2((double)pitch) - OCTAVE_OFFSET;
this->note = (int)round(l * 12);
} else {
this->note = -1;
}
update();
}
void SoundViewerWidget::paintEvent(QPaintEvent *event) {
(void)event;
QPainter painter(this);
painter.fillRect(rect(), Qt::transparent);
QRect temp = rect();
temp.moveTop(METER_HEIGHT);
painter.drawTiledPixmap(temp, pixmap);
// draw note
if (this->note >= 0 && this->note < 12 * NUM_OCTAVES) {
int octave = this->note / 12;
int nn = this->note % 12;
int adjust = 7 * KEY_WIDTH * octave;
QRect noteRect = keyRect[nn].adjusted(adjust, METER_HEIGHT, adjust, METER_HEIGHT);
painter.fillRect(noteRect, colors[this->channel]);
}
// draw volume
int meterLength = rect().width() / 2;
int left = (this->volume * this->panL) * meterLength;
int right = (this->volume * this->panR) * meterLength;
painter.fillRect(QRect(meterLength, 0, -left, METER_HEIGHT - METER_MARGIN), colors[this->channel]);
painter.fillRect(QRect(meterLength, 0, right, METER_HEIGHT - METER_MARGIN), colors[this->channel]);
}
SoundViewerWindow::SoundViewerWindow() {
setObjectName("sound-viewer");
setWindowTitle("Sound Viewer");
setGeometryString(&config().geometry.soundViewerWindow);
application.windowList.append(this);
layout = new QVBoxLayout;
layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
layout->setMargin(Style::WindowMargin);
layout->setSpacing(Style::WidgetSpacing);
layout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(layout);
for (int i = 0; i < 8; i++) {
QHBoxLayout *channelLayout = new QHBoxLayout;
if (SNES::DSP::SupportsChannelEnable) {
channelEnable[i] = new QCheckBox(QString("Channel %1").arg(i));
channelEnable[i]->setChecked(true);
connect(channelEnable[i], SIGNAL(stateChanged(int)), this, SLOT(synchronizeDSP()));
channelLayout->addWidget(channelEnable[i]);
} else {
channelEnable[i] = 0;
QLabel *noteLabel = new QLabel(QString("Channel %1").arg(i));
channelLayout->addWidget(noteLabel);
}
QFrame *line = new QFrame;
line->setFrameShape(QFrame::VLine);
line->setFrameShadow(QFrame::Sunken);
channelLayout->addWidget(line);
channelSource[i] = new QLabel;
channelLayout->addWidget(channelSource[i]);
channelLayout->addStretch();
channelEcho[i] = new QCheckBox("Echo");
channelLayout->addWidget(channelEcho[i]);
channelNoise[i] = new QCheckBox("Noise");
channelLayout->addWidget(channelNoise[i]);
channelPitchMod[i] = new QCheckBox("Pitch Mod.");
channelLayout->addWidget(channelPitchMod[i]);
layout->addLayout(channelLayout);
viewer[i] = new SoundViewerWidget(i);
layout->addWidget(viewer[i]);
}
}
void SoundViewerWindow::synchronize() {
uint8_t flg = SNES::dsp.read(0x6c);
uint8_t pmon = SNES::dsp.read(0x2d);
uint8_t non = SNES::dsp.read(0x3d);
uint8_t eon = SNES::dsp.read(0x4d);
for (int i = 0; i < 8; i++) {
viewer[i]->refresh();
if (SNES::DSP::SupportsChannelEnable) {
channelEnable[i]->setChecked(SNES::dsp.is_channel_enabled(i));
}
uint8_t source = SNES::dsp.read(0x4 + (i << 4));
channelSource[i]->setText(QString("Sample #%1").arg(source));
channelEcho[i]->setChecked((eon & (1<<i)) && !(flg & 0x20));
channelNoise[i]->setChecked(non & (1<<i));
channelPitchMod[i]->setChecked(pmon & (1<<i));
}
if (isVisible()) QTimer::singleShot(15, this, SLOT(synchronize()));
}
void SoundViewerWindow::setVisible(bool on) {
QWidget::setVisible(on);
if (on) synchronize();
}
void SoundViewerWindow::synchronizeDSP() {
if (SNES::DSP::SupportsChannelEnable) {
for (int i = 0; i < 8; i++) {
SNES::dsp.channel_enable(i, channelEnable[i]->isChecked());
}
effectToggleWindow->synchronize();
}
}