diff --git a/helper/aypsg2chime.py b/helper/aypsg2chime.py new file mode 100644 index 0000000..8db53c6 --- /dev/null +++ b/helper/aypsg2chime.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os, sys, pdb, struct, math +from argparse import ArgumentParser + +# PSG uses 20ms delays or multiples of 80ms delays +PSG_CLOCK = 1750000 +MS_IN_FF_COMMAND = 20 +MS_IN_MULTIPLE_COMMAND = 80 + +parser = ArgumentParser(prog='AYPSG2Chime', description="Converts PSG of AY-3-8910 to chime source for PIS-OSI") +parser.add_argument('infile') +parser.add_argument('name') +parser.add_argument('long_name') +args = parser.parse_args() + +INFILE=open(args.infile, 'rb').read() +if INFILE[0:4] != b"PSG\x1A": + print("Not PSG file") + os.exit(1) + +if INFILE[4] != 0x0: + print("New PSG format, todo: read freq, abort!") + os.exit(1) + +start_pos = 4 +while INFILE[start_pos] != 0xFF: + start_pos += 1 + +INDAT=INFILE[start_pos::] +pos = 0 +timestamp = 0 +eos = False +evts = [] + +NOISE_CHAN = 4 +AMPLITUDE_TO_DUTY=[ + 16, # 0, -> map to note off + 13, # 1, + 12, # 2, + 12, # 3, + 11, # 4, + 11, # 5, + 10, # 6, + 10, # 7, + 10, # 8, + 8, # 9, + 8, # A, + 8, # B, + 8, # C, + 6, # D, + 6, # E, + 4, # F, +] + +# A, B, C +tone_state=[True, True, True] +tone_ampli_state=[True, True, True] +noise_ampli_state=[True, True, True] +tone_raw_val = [0, 0, 0] +tone_val = [0, 0, 0] +noise_state=[False, False, False] +noise_val = 0 + +def prev_note_event(chan): + for i in range(1,len(evts)+1): + e = evts[-i] + if e.kind == "FREQ_SET" and e.chan == chan: + return e + elif e.kind == "DELAY": + return None + return None + +class Event(): + def __init__(self, kind, chan, arg): + self.kind = kind + self.chan = chan + self.arg = arg + + def __str__(self): + return f" {{{self.kind}, {str(self.chan)}, {str(self.arg)}}}," + +evts.append(Event("DUTY_SET", 0, 8)) +evts.append(Event("DUTY_SET", 1, 8)) +evts.append(Event("DUTY_SET", 2, 8)) + +VOL_THRESH = 0 + +while not eos and pos < len(INDAT): + cmd = INDAT[pos] + if cmd == 0xFD: + eos = True + elif cmd == 0xFF: + # delay TICKS_IN_FF_COMMAND + if len(evts) > 0 and evts[-1].kind == "DELAY": + evts[-1].arg += MS_IN_FF_COMMAND + else: + evts.append(Event("DELAY", 0, MS_IN_FF_COMMAND)) + elif cmd == 0xFE: + pos += 1 + count = INDAT[pos] + # delay TICKS_IN_MULTIPLE_COMMAND * count + if len(evts) > 0 and evts[-1].kind == "DELAY": + evts[-1].arg += MS_IN_MULTIPLE_COMMAND * count + else: + evts.append(Event("DELAY", 0, MS_IN_MULTIPLE_COMMAND * count)) + else: + regi = cmd + pos += 1 + valu = INDAT[pos] + # write `valu` to AY `regi` + # R10,11,12: AMPLITUDE CONTROL + if regi >= 10 and regi <= 12: + chan = regi - 10 + if valu & 0x10 == 0: + # Envelope mode, we don't have envelope + valu = 10 + else: + valu = valu & 0xF + valu = 0xF - valu + if valu <= VOL_THRESH: + if tone_state[chan] and tone_ampli_state[chan]: + evts.append(Event("FREQ_SET", chan, 0)) + tone_ampli_state[chan] = False + if noise_state[chan] and noise_ampli_state[chan]: + evts.append(Event("FREQ_SET", NOISE_CHAN, 0)) + noise_ampli_state[chan] = False + elif valu > VOL_THRESH: + if tone_val[chan] > 0 and tone_state[chan] and not tone_ampli_state[chan]: + evts.append(Event("FREQ_SET", chan, tone_val[chan])) + tone_ampli_state[chan] = True + if noise_state[chan] and not noise_ampli_state[chan] and noise_val > 0: + evts.append(Event("FREQ_SET", NOISE_CHAN, noise_val)) + noise_ampli_state[chan] = True + if tone_state[chan] and tone_ampli_state[chan] and valu > 0: + evts.append(Event("DUTY_SET", chan, AMPLITUDE_TO_DUTY[valu])) + # R6: NOISE CONTROL + elif regi == 6: + if noise_state[0] or noise_state[1] or noise_state[2]: + valu = valu & 0x1F + freq = PSG_CLOCK // 32 + if valu == 0: + freq //= 8 + else: + freq //= valu + freq //= 8 + noise_val = freq + evts.append(Event("FREQ_SET", NOISE_CHAN, freq)) + # R7: IO !ENABLE + elif regi == 7: + old_tone_state = tone_state.copy() + old_noise_state = (noise_state[0] and noise_ampli_state[0]) or (noise_state[1] and noise_ampli_state[1]) or (noise_state[2] and noise_ampli_state[2]) + for i in range(0,3): + tone_state[i] = ((valu & (1 << i)) == 0) + if tone_state[i] != old_tone_state[i]: + if tone_state[i] and tone_val[i] != 0: + evts.append(Event("FREQ_SET", i, tone_val[i])) + elif not tone_state[i]: + evts.append(Event("FREQ_SET", i, 0)) + noise_state[i] = (valu & (1 << (3 + i))) == 0 + new_noise_state = (noise_state[0] and noise_ampli_state[0]) or (noise_state[1] and noise_ampli_state[1]) or (noise_state[2] and noise_ampli_state[2]) + if new_noise_state and not old_noise_state and noise_val != 0: + evts.append(Event("FREQ_SET", NOISE_CHAN, noise_val)) + elif old_noise_state and not new_noise_state: + evts.append(Event("FREQ_SET", NOISE_CHAN, 0)) + # R0-R5: TONE CONTROL + elif regi >= 0 and regi <= 5: + chan = regi // 2 + is_fine = (regi % 2) == 0 + old_raw_val = tone_raw_val[chan] + if is_fine: + tone_raw_val[chan] &= 0xF00 + tone_raw_val[chan] |= valu + else: + tone_raw_val[chan] &= 0xFF + tone_raw_val[chan] |= ((valu & 0xF) << 8) + tone_val[chan] = PSG_CLOCK // 16 + if tone_raw_val[chan] == 0: + tone_val[chan] //= 0x1000 + else: + tone_val[chan] //= tone_raw_val[chan] + if old_raw_val != tone_raw_val[chan] and tone_state[chan] and tone_ampli_state[chan]: + existing_evt = prev_note_event(chan) + if existing_evt is not None: + existing_evt.arg = tone_val[chan] + else: + evts.append(Event("FREQ_SET", chan, tone_val[chan])) + + pos += 1 + +print('#include ') +print('extern "C" const POMFHeader POMF_HEAD = {') +print(' POMF_MAGIC_FILE,') +print(' POMF_CURVER,') +print(' "'+args.name+'",') +print(' "'+args.long_name+'",') +print('};') +print('') +print('extern "C" const melody_item_t POMF_TUNE[] = {') +for e in evts: + if e.kind == "DELAY" and e.arg > 1000: + x = e.arg + e.arg = 1000 + while x > 1000: + print(str(e)) + x -= 1000 + e.arg = x + print(str(e)) +print("};") +print('') \ No newline at end of file diff --git a/include/devices/big_clock.h b/include/devices/big_clock.h index a224fa9..77c0708 100644 --- a/include/devices/big_clock.h +++ b/include/devices/big_clock.h @@ -13,7 +13,7 @@ #define HAS_KEYPAD // ↓ Looks like shite on the plasma display -#define COMPOSABLE_NO_EVENODD +// #define COMPOSABLE_NO_EVENODD // let's see how it looks // Plasma Information System OS (not DOS, there's no disk in it!) #define PRODUCT_NAME "PIS-OS" diff --git a/include/devices/mid_clock_noritake.h b/include/devices/mid_clock_noritake.h index 08673ab..ff2a0e8 100644 --- a/include/devices/mid_clock_noritake.h +++ b/include/devices/mid_clock_noritake.h @@ -3,7 +3,6 @@ #include #include -// #define HAS_OTAFVU #define HAS_OUTPUT_GU7000 #define HAS_DISPLAY_BLANKING #define HAS_MOTION_SENSOR @@ -13,9 +12,6 @@ #define HAS_VARYING_BRIGHTNESS #define HAS_LIGHT_SENSOR -// ↓ Looks like shite on the VFD display -#define COMPOSABLE_NO_EVENODD - // Plasma Information System OS (not DOS, there's no disk in it!) #define PRODUCT_NAME "PIS-OS" #define PRODUCT_VERSION "5.3" diff --git a/include/devices/mid_clock_noritake_wide.h b/include/devices/mid_clock_noritake_wide.h index aaf1196..a1345eb 100644 --- a/include/devices/mid_clock_noritake_wide.h +++ b/include/devices/mid_clock_noritake_wide.h @@ -3,7 +3,6 @@ #include #include -#define HAS_OTAFVU #define HAS_OUTPUT_GU7000 #define HAS_DISPLAY_BLANKING // #define HAS_TEMP_SENSOR @@ -13,9 +12,6 @@ #define HAS_VARYING_BRIGHTNESS #define HAS_LIGHT_SENSOR -// ↓ Looks like shite on the VFD display -#define COMPOSABLE_NO_EVENODD - // Plasma Information System OS (not DOS, there's no disk in it!) #define PRODUCT_NAME "PIS-dev" #define PRODUCT_VERSION "5.3" diff --git a/include/views/common/view.h b/include/views/common/view.h index 1f24307..70d16d2 100644 --- a/include/views/common/view.h +++ b/include/views/common/view.h @@ -21,6 +21,7 @@ class Composable: public Renderable { int x_offset = 0; int width = -1; bool hidden = false; + // NB: Experimental. Current implementation is less flickery than just showing only every other frame, but doesn't play nice with alpha channel items. bool gray = false; }; @@ -57,7 +58,30 @@ class Composite: public Composable { for(Composable *r: composables) { if(r->hidden) continue; #ifndef COMPOSABLE_NO_EVENODD - if(r->gray && !even_odd) continue; + if(r->gray) { + int w = (r->width >= 0 ? r->width : fb->get_width()); + size_t bufsz = w * sizeof(uint16_t); + fanta_buffer_t tmpbuf = (fanta_buffer_t) gralloc(bufsz); + if(tmpbuf) { + fanta_buffer_t mask = (fanta_buffer_t) gralloc(bufsz); + if(mask) { + bool dirty = false; + FantaManipulator *tmp = new FantaManipulator(tmpbuf, bufsz, w, 16, NULL, &dirty); + tmp->clear(); + dirty = false; + r->render(tmp); + if(dirty) { + memcpy(mask, tmpbuf, bufsz); + for(int i = 0; i < bufsz; i++) { mask[i] &= (even_odd ? 0b01010101 : 0b10101010); } + fb->put_fanta(tmp->get_fanta(), r->x_offset, 0, tmp->get_width(), tmp->get_height(), mask); + } + delete tmp; + free(mask); + } else ESP_LOGE("COMP", "Failed to alloc MASK of %i bytes", bufsz); + free(tmpbuf); + } else ESP_LOGE("COMP", "Failed to alloc TMP of %i bytes", bufsz); + continue; + } #endif if(r->x_offset <= 0 && r->width < 0) { r->render(fb); diff --git a/include/views/idle_screens/simple_clock.h b/include/views/idle_screens/simple_clock.h index b7206a4..88da103 100644 --- a/include/views/idle_screens/simple_clock.h +++ b/include/views/idle_screens/simple_clock.h @@ -19,5 +19,7 @@ class SimpleClock: public Screen, DroppingDigits { char separator; bool blink_separator; bool show_seconds; + bool time_not_set; + bool not_set_blink_phase; AmPmLabel * pm_label; }; \ No newline at end of file diff --git a/src/views/idle_screens/simple_clock.cpp b/src/views/idle_screens/simple_clock.cpp index 4c40b63..dd062e9 100644 --- a/src/views/idle_screens/simple_clock.cpp +++ b/src/views/idle_screens/simple_clock.cpp @@ -86,6 +86,8 @@ SimpleClock::SimpleClock(): DroppingDigits() { add_composable(pm_label); blink_separator = prefs_get_bool(PREFS_KEY_BLINK_SEPARATORS); show_seconds = prefs_get_bool(PREFS_KEY_SHOW_SECONDS); + time_not_set = true; + not_set_blink_phase = false; } void add_one_second(tk_time_of_day_t * time) { @@ -123,6 +125,9 @@ void subtract_one_second(tk_time_of_day_t * time) { } void SimpleClock::step() { + tk_date_t date = get_current_date(); + time_not_set = (date.year == 1970 && date.month == 1); + now = get_current_time_precise(); next_time = now; @@ -132,16 +137,20 @@ void SimpleClock::step() { phase = 0; - // When animating precisely on-the-millisecond, the digit does an annoying blink of the last value sometimes - // So animate the first half in the "previous" second, and the second half in the "next" second - if(remain_ms < (steps/2) * ms_per_step) { - phase = (steps/2) - (remain_ms / ms_per_step); - add_one_second(&next_time); - } else if (now.millisecond < (steps/2) * ms_per_step) { - phase = (steps/2) + (now.millisecond / ms_per_step); - subtract_one_second(&now); + if(time_not_set) { + not_set_blink_phase = (remain_ms < 500); + } else { + // When animating precisely on-the-millisecond, the digit does an annoying blink of the last value sometimes + // So animate the first half in the "previous" second, and the second half in the "next" second + if(remain_ms < (steps/2) * ms_per_step) { + phase = (steps/2) - (remain_ms / ms_per_step); + add_one_second(&next_time); + } else if (now.millisecond < (steps/2) * ms_per_step) { + phase = (steps/2) + (now.millisecond / ms_per_step); + subtract_one_second(&now); + } + phase = EASING_CURVE[phase]; } - phase = EASING_CURVE[phase]; if(!prefs_get_bool(PREFS_KEY_DISP_24_HRS)) { bool now_pm = false; @@ -168,19 +177,22 @@ void SimpleClock::render(FantaManipulator *framebuffer) { int left_offset = framebuffer->get_width() / 2 - text_width / 2; pm_label->x_offset = left_offset - 11; - draw_dropping_number(framebuffer, now.hour, next_time.hour, phase, left_offset); + if(!time_not_set || !not_set_blink_phase) + draw_dropping_number(framebuffer, now.hour, next_time.hour, phase, left_offset); left_offset += 2 * font->width; framebuffer->put_glyph(font, separator, left_offset, 0); left_offset += font->width; - draw_dropping_number(framebuffer, now.minute, next_time.minute, phase, left_offset); + if(!time_not_set || !not_set_blink_phase) + draw_dropping_number(framebuffer, now.minute, next_time.minute, phase, left_offset); left_offset += 2 * font->width; if(show_seconds) { framebuffer->put_glyph(font, separator, left_offset, 0); left_offset += font->width; - draw_dropping_number(framebuffer, now.second, next_time.second, phase, left_offset); + if(!time_not_set || !not_set_blink_phase) + draw_dropping_number(framebuffer, now.second, next_time.second, phase, left_offset); } Screen::render(framebuffer);