419 lines
9.9 KiB
C++
419 lines
9.9 KiB
C++
|
/*
|
||
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||
|
* Copyright (C) 2016 Furrtek
|
||
|
*
|
||
|
* This file is part of PortaPack.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation; either version 2, 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 General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; see the file COPYING. If not, write to
|
||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||
|
* Boston, MA 02110-1301, USA.
|
||
|
*/
|
||
|
|
||
|
#include <ch.h>
|
||
|
|
||
|
#include "demofont.hpp"
|
||
|
#include "ymdata.hpp"
|
||
|
|
||
|
#include "cpld_update.hpp"
|
||
|
#include "portapack.hpp"
|
||
|
#include "audio.hpp"
|
||
|
#include "event_m0.hpp"
|
||
|
|
||
|
#include "ui_about.hpp"
|
||
|
#include "touch.hpp"
|
||
|
#include "sine_table.hpp"
|
||
|
|
||
|
#include "portapack_shared_memory.hpp"
|
||
|
#include "portapack_persistent_memory.hpp"
|
||
|
#include "lpc43xx_cpp.hpp"
|
||
|
|
||
|
#include <math.h>
|
||
|
#include <cstring>
|
||
|
|
||
|
using namespace lpc43xx;
|
||
|
using namespace portapack;
|
||
|
|
||
|
namespace ui {
|
||
|
|
||
|
void AboutView::on_show() {
|
||
|
transmitter_model.set_tuning_frequency(1337000000); // TODO: Change
|
||
|
transmitter_model.set_baseband_configuration({
|
||
|
.mode = 0,
|
||
|
.sampling_rate = 1536000,
|
||
|
.decimation_factor = 1,
|
||
|
});
|
||
|
transmitter_model.set_rf_amp(true);
|
||
|
transmitter_model.set_lna(40);
|
||
|
transmitter_model.set_vga(40);
|
||
|
transmitter_model.set_baseband_bandwidth(1750000);
|
||
|
transmitter_model.enable();
|
||
|
|
||
|
baseband::set_audiotx_data(32, 50, false, 0);
|
||
|
|
||
|
//audio::headphone::set_volume(volume_t::decibel(0 - 99) + audio::headphone::volume_range().max);
|
||
|
}
|
||
|
|
||
|
void AboutView::render_video() {
|
||
|
uint8_t p, r, luma, chroma, cy;
|
||
|
ui::Color cc;
|
||
|
char ch;
|
||
|
float s;
|
||
|
|
||
|
// Send framebuffer to LCD. Gotta go fast !
|
||
|
display.render_box({30, 112}, {180, 72}, framebuffer);
|
||
|
|
||
|
// Clear framebuffer to black
|
||
|
memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
|
||
|
|
||
|
// Drum hit palette animation
|
||
|
if (drum > 1) drum--;
|
||
|
|
||
|
// Render copper bars from Y buffer
|
||
|
for (p = 0; p < 72; p++) {
|
||
|
luma = copperbuffer[p] & 0x0F; // 0 is transparent
|
||
|
if (luma) {
|
||
|
chroma = copperbuffer[p]>>4;
|
||
|
cc = ui::Color(std::min((coppercolor[chroma][0]/luma)*drum,255), std::min((coppercolor[chroma][1]/luma)*drum,255), std::min((coppercolor[chroma][2]/luma)*drum,255));
|
||
|
for (r = 0; r < 180; r++)
|
||
|
framebuffer[(p*180)+r] = cc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Scroll in/out state machine
|
||
|
if (anim_state == 0) {
|
||
|
// Scroll in
|
||
|
if (ofy < 8) {
|
||
|
ofy++;
|
||
|
anim_state = 0;
|
||
|
} else {
|
||
|
anim_state = 1;
|
||
|
}
|
||
|
if (ofx < (int16_t)(180 - (strlen(credits[credits_index].name) * 16) - 8)) {
|
||
|
ofx += 8;
|
||
|
anim_state = 0;
|
||
|
}
|
||
|
} else if (anim_state == 1) {
|
||
|
// Just wait
|
||
|
if (credits_timer == (30*3)) {
|
||
|
credits_timer = 0;
|
||
|
anim_state = 2;
|
||
|
} else {
|
||
|
credits_timer++;
|
||
|
}
|
||
|
} else {
|
||
|
// Scroll out
|
||
|
if (credits[credits_index].change == true) {
|
||
|
if (ofy > -24) {
|
||
|
ofy--;
|
||
|
anim_state = 2;
|
||
|
} else {
|
||
|
anim_state = 0;
|
||
|
}
|
||
|
} else {
|
||
|
anim_state = 0;
|
||
|
}
|
||
|
if (ofx < 180) {
|
||
|
ofx += 8;
|
||
|
anim_state = 2;
|
||
|
}
|
||
|
|
||
|
// Switch to next text
|
||
|
if (anim_state == 0) {
|
||
|
if (credits_index == 9)
|
||
|
credits_index = 0;
|
||
|
else
|
||
|
credits_index++;
|
||
|
ofx = -(strlen(credits[credits_index].name) * 16) - 16;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sine text ("role")
|
||
|
p = 0;
|
||
|
while ((ch = credits[credits_index].role[p])) {
|
||
|
draw_demoglyph({(ui::Coord)(8+(p*16)), (ui::Coord)(ofy+(sine_table_f32[((p*16)+(phase>>5))&0xFF] * 8))}, ch, paletteA);
|
||
|
p++;
|
||
|
}
|
||
|
|
||
|
// Scroll text (name)
|
||
|
p = 0;
|
||
|
while ((ch = credits[credits_index].name[p])) {
|
||
|
draw_demoglyph({(ui::Coord)(ofx+(p*16)), 56}, ch, paletteB);
|
||
|
p++;
|
||
|
}
|
||
|
|
||
|
// Clear bars Y buffer
|
||
|
memset(copperbuffer, 0, 72);
|
||
|
|
||
|
// Render bars to Y buffer
|
||
|
for (p = 0; p < 5; p++) {
|
||
|
cy = copperbars[p];
|
||
|
for (r = 0; r < 16; r++)
|
||
|
copperbuffer[cy+r] = copperluma[r] + (p<<4);
|
||
|
}
|
||
|
|
||
|
// Animate bars positions
|
||
|
for (p = 0; p < 5; p++) {
|
||
|
s = sine_table_f32[((p*32)+(phase/24))&0xFF];
|
||
|
s += sine_table_f32[((p*16)+(phase/35))&0xFF];
|
||
|
copperbars[p] = 28+(uint8_t)(s * 14);
|
||
|
}
|
||
|
|
||
|
phase += 128;
|
||
|
}
|
||
|
|
||
|
void AboutView::draw_demoglyph(ui::Point p, char ch, ui::Color * pal) {
|
||
|
uint8_t x, y, c, cl, cr;
|
||
|
uint16_t che;
|
||
|
int16_t lbx, il;
|
||
|
|
||
|
// Map ASCII to font bitmap
|
||
|
if ((ch >= 32) || (ch < 96))
|
||
|
che = char_map[ch-32];
|
||
|
else
|
||
|
che = 0xFF;
|
||
|
|
||
|
if (che < 0xFF) {
|
||
|
che = (che * 128) + 48; // Start in bitmap
|
||
|
|
||
|
il = (180 * p.y) + p.x; // Start il framebuffer
|
||
|
|
||
|
for (y = 0; y < 16; y++) {
|
||
|
if (p.y + y >= 72) break; // Over bottom of framebuffer, abort
|
||
|
if (p.y + y >= 0) {
|
||
|
for (x = 0; x < 8; x++) {
|
||
|
c = demofont_bin[x+(y*8)+che]; // Split byte in 2 4BPP pixels
|
||
|
cl = c >> 4;
|
||
|
cr = c & 0x0F;
|
||
|
lbx = p.x + (x*2);
|
||
|
if (cl && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cl];
|
||
|
lbx++;
|
||
|
il++;
|
||
|
if (cr && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cr];
|
||
|
il++;
|
||
|
}
|
||
|
il += 180-16;
|
||
|
} else {
|
||
|
il += 180;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AboutView::render_audio() {
|
||
|
uint8_t i, ymdata;
|
||
|
uint16_t ym_render_cnt;
|
||
|
|
||
|
// This is heavily inspired by MAME's ay8910.cpp and the YM2149's datasheet
|
||
|
|
||
|
// Render 1024 music samples
|
||
|
for (ym_render_cnt = 0; ym_render_cnt < 1024; ym_render_cnt++) {
|
||
|
|
||
|
// Update registers at 48000/960 = 50Hz
|
||
|
if (ym_sample_cnt == 0) {
|
||
|
// "Decompress" on the fly and update YM registers
|
||
|
for (i = 0; i < 14; i++) {
|
||
|
if (!ym_regs[i].cnt) {
|
||
|
// New run
|
||
|
ymdata = ymdata_bin[ym_regs[i].ptr++];
|
||
|
ym_regs[i].cnt = ymdata & 0x7F;
|
||
|
if (ymdata & 0x80) {
|
||
|
ym_regs[i].same = true;
|
||
|
ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
|
||
|
} else {
|
||
|
ym_regs[i].same = false;
|
||
|
}
|
||
|
// Detect drum on channel B
|
||
|
if (i == 3)
|
||
|
if (ym_regs[3].value > 2) drum = 4;
|
||
|
}
|
||
|
if (ym_regs[i].same == false) {
|
||
|
ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
|
||
|
if (i == 13) {
|
||
|
// Update envelope attributes
|
||
|
ym_env_att = (ym_regs[13].value & 4) ? 0x1F : 0x00;
|
||
|
if (!(ym_regs[13].value & 8)) {
|
||
|
ym_env_hold = 1;
|
||
|
ym_env_alt = ym_env_att;
|
||
|
} else {
|
||
|
ym_env_hold = ym_regs[13].value & 1;
|
||
|
ym_env_alt = ym_regs[13].value & 2;
|
||
|
}
|
||
|
// Reset envelope counter
|
||
|
ym_env_step = 0x1F;
|
||
|
ym_env_holding = 0;
|
||
|
ym_env_vol = (ym_env_step ^ ym_env_att);
|
||
|
}
|
||
|
}
|
||
|
ym_regs[i].cnt--;
|
||
|
}
|
||
|
ym_frame++;
|
||
|
}
|
||
|
|
||
|
// Square wave oscillators
|
||
|
// 2457600/16/48000 = 3.2, but 4 sounds better than 3...
|
||
|
for (i = 0; i < 3; i++) {
|
||
|
ym_osc_cnt[i] += 4;
|
||
|
if (ym_osc_cnt[i] >= (ym_regs[i*2].value | ((ym_regs[(i*2)+1].value & 0x0f) << 8))) {
|
||
|
ym_osc_cnt[i] = 0;
|
||
|
ym_osc_out[i] ^= 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Noise generator
|
||
|
ym_noise_cnt += 4;
|
||
|
if (ym_noise_cnt >= ((ym_regs[6].value & 0x1F) * 2)) {
|
||
|
ym_noise_cnt = 0;
|
||
|
ym_rng ^= (((ym_rng & 1) ^ ((ym_rng >> 3) & 1)) << 17);
|
||
|
ym_rng >>= 1;
|
||
|
}
|
||
|
|
||
|
// Mix tones and noise
|
||
|
for (i = 0; i < 3; i++)
|
||
|
ym_ch[i] = (ym_osc_out[i] | ((ym_regs[7].value >> i) & 1)) & ((ym_rng & 1) | ((ym_regs[7].value >> (i + 3)) & 1));
|
||
|
|
||
|
// Envelope generator
|
||
|
if (!ym_env_holding) {
|
||
|
ym_env_cnt += 8;
|
||
|
if (ym_env_cnt >= (ym_regs[11].value | (ym_regs[12].value<<8))) {
|
||
|
ym_env_cnt = 0;
|
||
|
ym_env_step--;
|
||
|
if (ym_env_step < 0) {
|
||
|
if (ym_env_hold) {
|
||
|
if (ym_env_alt)
|
||
|
ym_env_att ^= 0x1F;
|
||
|
ym_env_holding = 1;
|
||
|
ym_env_step = 0;
|
||
|
} else {
|
||
|
if (ym_env_alt && (ym_env_step & 0x20))
|
||
|
ym_env_att ^= 0x1F;
|
||
|
ym_env_step &= 0x1F;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ym_env_vol = (ym_env_step ^ ym_env_att);
|
||
|
|
||
|
ym_out = 0;
|
||
|
for (i = 0; i < 3; i++) {
|
||
|
if (ym_regs[i + 8].value & 0x10) {
|
||
|
// Envelope mode
|
||
|
ym_out += (ym_ch[i] ? ym_env_vol : 0);
|
||
|
} else {
|
||
|
// Fixed mode
|
||
|
ym_out += (ym_ch[i] ? (ym_regs[i + 8].value & 0x0F) : 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ym_buffer[ym_render_cnt] = (ym_out * 2) - 45;
|
||
|
|
||
|
if (ym_sample_cnt < 960) {
|
||
|
ym_sample_cnt++;
|
||
|
} else {
|
||
|
ym_sample_cnt = 0;
|
||
|
}
|
||
|
|
||
|
// Loop
|
||
|
if (ym_frame == ym_frames) ym_init();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AboutView::update() {
|
||
|
if (framebuffer) {
|
||
|
// Update 1 out of 2 frames, 60Hz is very laggy
|
||
|
if (refresh_cnt & 1) render_video();
|
||
|
refresh_cnt++;
|
||
|
}
|
||
|
|
||
|
// Slowly increase volume to avoid jumpscare
|
||
|
if (headphone_vol < (70 << 2)) {
|
||
|
audio::headphone::set_volume(volume_t::decibel((headphone_vol/4) - 99) + audio::headphone::volume_range().max);
|
||
|
headphone_vol++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AboutView::ym_init() {
|
||
|
uint8_t reg;
|
||
|
|
||
|
for (reg = 0; reg < 14; reg++) {
|
||
|
ym_regs[reg].cnt = 0;
|
||
|
// Pick up start pointers for each YM registers RLE blocks
|
||
|
ym_regs[reg].ptr = ((uint16_t)(ymdata_bin[(reg*2)+3])<<8) + ymdata_bin[(reg*2)+2];
|
||
|
ym_regs[reg].same = false; // Useless ?
|
||
|
ym_regs[reg].value = 0; // Useless ?
|
||
|
}
|
||
|
|
||
|
ym_frame = 0;
|
||
|
}
|
||
|
|
||
|
AboutView::AboutView(
|
||
|
NavigationView& nav
|
||
|
)
|
||
|
{
|
||
|
uint8_t p, c;
|
||
|
|
||
|
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
||
|
|
||
|
add_children({ {
|
||
|
&text_title,
|
||
|
&text_firmware,
|
||
|
&text_cpld_hackrf,
|
||
|
&text_cpld_hackrf_status,
|
||
|
&button_ok,
|
||
|
} });
|
||
|
|
||
|
if( cpld_hackrf_verify_eeprom() ) {
|
||
|
text_cpld_hackrf_status.set(" OK");
|
||
|
} else {
|
||
|
text_cpld_hackrf_status.set("BAD");
|
||
|
}
|
||
|
|
||
|
// Politely ask for about 26kB
|
||
|
framebuffer = (ui::Color *)chHeapAlloc(0x0, 180 * 72 * sizeof(ui::Color));
|
||
|
|
||
|
if (framebuffer) {
|
||
|
memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));
|
||
|
|
||
|
// Copy original font palette
|
||
|
c = 0;
|
||
|
for (p = 0; p < 48; p+=3)
|
||
|
paletteA[c++] = ui::Color(demofont_bin[p], demofont_bin[p+1], demofont_bin[p+2]);
|
||
|
|
||
|
// Increase red in another one
|
||
|
c = 0;
|
||
|
for (p = 0; p < 48; p+=3)
|
||
|
paletteB[c++] = ui::Color(std::min(demofont_bin[p]+64, 255), demofont_bin[p+1], demofont_bin[p+2]);
|
||
|
}
|
||
|
|
||
|
// Init YM synth
|
||
|
ym_frames = ((uint16_t)(ymdata_bin[1])<<8) + ymdata_bin[0];
|
||
|
ym_init();
|
||
|
|
||
|
|
||
|
button_ok.on_select = [this,&nav](Button&){
|
||
|
if (framebuffer) chHeapFree(framebuffer); // Do NOT forget this
|
||
|
nav.pop();
|
||
|
};
|
||
|
}
|
||
|
|
||
|
AboutView::~AboutView() {
|
||
|
transmitter_model.disable();
|
||
|
baseband::shutdown();
|
||
|
}
|
||
|
|
||
|
void AboutView::focus() {
|
||
|
button_ok.focus();
|
||
|
}
|
||
|
|
||
|
} /* namespace ui */
|