/* * 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 #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 #include using namespace lpc43xx; using namespace portapack; namespace ui { void AboutView::on_show() { transmitter_model.set_tuning_frequency(92200000); 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, 15, 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 */