/* Copyright (c) Günter Woigk 2008 - 2009 mailto:kio@little-bat.de 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. Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holder not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holder makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define SAFE 3 #define LOG 1 #include "CrtcJupiter.h" #include "Machine.h" #include "Z80/Z80.h" #include "ScreenMono.h" #include "Dsp.h" #include "ZxInfo.h" #include "Kbd.h" #include "AlertPanel.h" INIT_MSG /* io adress and bits and mic_out and beeper: verified with circuit diagram kio 2008-05-31 The whole Jupiter Ace has only one io address which decodes A0 only. Reading returns keys from the keyboard and the ear_in state in D5. Writing sets the mic_out state from bit D3. Reading port A0 switches off the speaker and writing switches it on. note: there is a small capacitor between speaker and mic socket, which might result in a faint signal from the speaker beeing coupled into mic-out. or vive versa. to be investigated... The speaker and the mic_out line are set by different methods and may differ. But zxsp supports only a common audio-out path for audio and tape output. There are 3 modes of audio-out handling: 1 - mic_out_only 2 - speaker_only 3 - mixed_audio mixed_audio: mixed_audio works like this: out($FE) toggles the audio output state in($FE) resets the oudio output state this works if out() is only issued to toggle the state and in() occurs only at the same phase of the audio signal. this should work with the rom beep and rom tape save routine. beep: the rom routine repeatedly issues in() and out() => in() resets, out() toggles, thus sets, the next in() resets and so forth. save: the audio-out state is assumed to be low at subroutine entry: the last io action must have been an in(), e.g. from a keyboard scan. then repeated pairs of out() for each bit toggle audio-out high and back low again. the in() for the user break test comes after each full byte, and thus occurs when audio-out is low: the audio-out state is not changed by this in(). limitations: - if the audio-out state is not low at the start - if an odd number of pulses is generated for the pilot sequence - if an odd number of pulses is used for the sync pulse - if bits are not saved as pairs of out() - if multiple out()s are done for the same mic_out state - if the break test in() occurs in the middle of a bit then mixed_audio mode will miss or insert an edge and produce a buggy signal. Timing: XTAL = 6.5 MHz PIXEL.CLK = XTAL CPU.CLK = CNT0 = XTAL÷2 CHAR.CLK = CNT2 = XTAL÷8 CLK3 to CLK7 serve as vram column address CNT3 = CPU.CLK÷8 CNT4 = CPU.CLK÷16 Z10A.CLR = CNT5 & CNT7 & CNT8 = 1+4+8 = 13 CNT8 = CNT4÷12 = CPU.CLK÷(16*13) = CHAR.CLK÷(4*13) = CHAR.CLK÷(32+8+4+8) ROW.CLK = CNT8 = CHAR.CLK÷52 = CPU.CLK÷208 = XTAL÷416 CNT9 to CNT11 serve as cram row address CNT12 to CNT16 serve as vram rom address CNT11 = ROW.CLK÷8 Z11AB.CLR = CNT12+CNT13+CNT14+CNT17 = 1+2+4+32 = 39 FRAME.CLK = CNT17 = CNT11÷39 = ROW.CLK÷(8*39) = ROW.CLK÷312 Interrupt: INT = CNT12+CNT13+CNT14+CNT15+CNT16 INT = CHAR.ROW 1+2+4+8+16 = CHAR.ROW 31: from start to end: 8*52*4 CPU.CLKs */ #define o_addr "----.----.----.---0" // übliche Adresse: $FE #define MIC_OUT_BIT 3 #define MIC_OUT_MASK (1<<3) // bits 0-4: 5 keys from keyboard (low=pressed), row selected by A8..A15 // bits 6+7: floating #define i_addr "----.----.----.---0" // übliche Adresse: $FE #define EAR_IN_BIT 5 #define EAR_IN_MASK (1<<5) // read port: switch off spkr // write port: switch on spkr CrtcJupiter::~ CrtcJupiter() { } CrtcJupiter::CrtcJupiter ( Machine* m, uint framerate ) : CrtcMono(m,isa_CrtcJupiter,"ULA (Jupiter Ace)") { XXASSERT(framerate==50); // 60 Hz not yet supported // ZEILEN: 24 + 4/7 + 1 + 4/7 (60/50 HZ) // ZEICHEN: 32 + 8 + 4 + 8 // BILD + VOR + SYNC + NACH // frame_w = 52; // 32+8+4+8 frame width [bytes] == address offset per scan line frame_h = 312; // (24+7+1+7)*8 frame height [scan line] screen_w = 32; // screen width [bytes] screen_h = 192; // (24*8) screen height [scan lines] screen_x0 = 0; // hor. screen offset inside frame scan lines [bytes] screen_y0 = 64; // (1+7)*8 vert. screen offset inside frame [scan lines] audio_mode = mixed_audio; // or: read from prefs? } void CrtcJupiter::Init(long cc) { XXLogIn("CrtcJupiter:Init"); CrtcMono::Init(cc); border_color = 0x00; // black } void CrtcJupiter::Reset ( Time t, long cc ) { XLogIn("CrtcJupiter::Reset"); CrtcMono::Reset(t,cc); } void CrtcJupiter::Output ( Time now, long, uint16 addr, uint8 byte ) { if( addr&0x0001 ) return; // not my address // out($FE) switches on the beeper current // bit 3 sets MIC_OUT state // BEEPER: // switch on beeper current: // mixed_audio: toggle audio-out // speaker_only: set audio-out to ON // mic_out_only: set audio-out acc. to D3 Sample new_sample = audio_mode==mixed_audio ? beeper.volume-beeper.current_sample : audio_mode==speaker_only || (byte&MIC_OUT_MASK) ? beeper.current_sample : 0.0; if( new_sample != beeper.current_sample ) { Dsp::OutputSamples( beeper.current_sample, beeper.last_sample_time, now ); beeper.last_sample_time = now; beeper.current_sample = new_sample; } // AUTOSTART/STOP TAPE: if( (ula_out_byte^byte)&(MIC_OUT_MASK) ) ear_out_count++; ula_out_byte = byte; } void CrtcJupiter::Input ( Time now, long, uint16 addr, uint8& byte, uint8& mask ) { if( addr&0x0001 ) return; // not my address // in($FE) switches off the beeper current // bits 0-4 come from the keyboard // bit 5 is the signal from the ear socket // bits 6+7 are (probably) always 1 // BEEPER: // switch off beeper current: // mic_out_only: in(FE) does not affect audio-out // speaker_only: set audio-out to OFF // mixed_audio: set audio-out to OFF if( beeper.current_sample!=0.0 && audio_mode != mic_out_only ) { Dsp::OutputSamples( beeper.current_sample, beeper.last_sample_time, now ); beeper.last_sample_time = now; beeper.current_sample = 0.0; } // KEYBOARD: // merge in the keys #if 0 if( machine==frontMachine && keymap->isKeyPressed() ) { uint8* p = &keymap->row[0]; uint8 minor = (~addr)>>8; if( minor&0x81 ) // bottom row? => convert key layout zxsp -> jupiter { uint8 zxsp0 = p[0]; uint8 zxsp7 = p[7]; // uint8 jup0 = (zxsp0&~0x01/*CSH*/) | (zxsp7&~0x02/*SSH*/) | ((zxsp0<<1)&~0x1C/*Y,X,C*/); // uint8 jup7 = (zxsp7&~0x01/*SPC*/) | (zxsp0&~0x10/* V */) | ((zxsp7>>1)&~0x0E/*B,N,M*/); if( minor&0x01 ) { byte &= (zxsp0|~0x01/*CSH*/) & (zxsp7|~0x02/*SSH*/) & ((zxsp0<<1)|~0x1C/*Y,X,C*/); } if( minor&0x80 ) { byte &= (zxsp7|~0x01/*SPC*/) & (zxsp0|~0x10/* V */) & ((zxsp7>>1)|~0x0E/*B,N,M*/); } minor &= ~0x81; } p++; minor >>= 1; while ( minor ) { if (minor&1) byte &= *p; minor>>=1; p++; } } #endif // insert bit from mic socket // signal from EAR input is read into bit 5. Sample const threshold = 0.0; // to be verified ulong a = ulong(now*samples_per_second); if( a>=DSP_SAMPLES_PER_BUFFER+DSP_SAMPLES_STITCHING ) { XXTRAP( (long)a<0 ); ShowAlert( "Sample input beyond dsp buffer: +%i\n", int(a-DSP_SAMPLES_PER_BUFFER) ); if(0.0getRam(); uint16* vram = ram.Data(); // videoram = 1st 1K page (768 bytes = 24*32 char used) bit 7: inverse uint16* cram = ram.Data()+1024; // charram = 2nd 1K page (1K = 128 char * 8 bytes/char ) // vram[0*32-0] |= 'A'; // TEST // vram[1*32+1] |= 'B'; // TEST // vram[2*32+2] |= 'C'; // TEST // vram[3*32+3] |= 'D'; // TEST memset( frame_data, 0x00, frame_w*frame_h ); uint8* z = frame_data + screen_x0 + screen_y0 * frame_w; // first byte of screen$ data for( int charrow=0; charrow<24; charrow++ ) { for( int y = 0; y<8; y++ ) { for( int x=0; x<32; x++ ) { ushort c = vram[charrow*32+x]; // current character - note: data byte is in low byte of ushort char b = cram[(c&0x007F)*8+y]; // current pixel octet from character glyph *z++ = (char(c)>>7) ^ b; } z += frame_w - 32; } } // frame_data[64*52-0] = 0xFF; // TEST // frame_data[65*52+1] = 0xAA; // TEST // frame_data[66*52+2] = 0x55; // TEST // frame_data[67*52+3] = 0xFF; // TEST machine->getCpu()->SetInterrupt( 0, 8*52*4 ); screen_mono->NextFrame(frame_data,frame_w,frame_h,screen_w,screen_h,screen_x0,screen_y0); return GetCpuCycleForFrameFlyback(); // cc_per_frame for last frame } void CrtcJupiter::SaveToFile ( int fd ) const throw(file_error,bad_alloc) { CrtcMono::SaveToFile(fd); write_data(fd,&audio_mode); } void CrtcJupiter::LoadFromFile ( int fd ) throw(file_error,bad_alloc) { CrtcMono::LoadFromFile(fd); read_data(fd,&audio_mode); }