remove Television interface / renamed Reference to Television

the interface was becoming too large and was a remnant of a very early
development phase. the gui interface type has replaced that concept.
This commit is contained in:
JetSetIlly 2020-10-23 15:17:29 +01:00
parent 6ad6d31a6c
commit 63da4073d9
19 changed files with 700 additions and 838 deletions

View file

@ -56,7 +56,7 @@ type Debugger struct {
lastResult *disassembly.Entry
// gui, tv and terminal
tv television.Television
tv *television.Television
scr gui.GUI
term terminal.Terminal
@ -138,7 +138,7 @@ type Debugger struct {
// NewDebugger creates and initialises everything required for a new debugging
// session. Use the Start() method to actually begin the session.
func NewDebugger(tv television.Television, scr gui.GUI, term terminal.Terminal, useSavekey bool) (*Debugger, error) {
func NewDebugger(tv *television.Television, scr gui.GUI, term terminal.Terminal, useSavekey bool) (*Debugger, error) {
var err error
dbg := &Debugger{

View file

@ -27,81 +27,6 @@ import (
"github.com/jetsetilly/gopher2600/hardware/television"
)
type mockTV struct{}
func (t *mockTV) String() string {
return ""
}
func (t *mockTV) Snapshot() television.TelevisionState {
return nil
}
func (t *mockTV) Plumb(_ television.TelevisionState) {
}
func (t *mockTV) AddPixelRenderer(_ television.PixelRenderer) {
}
func (t *mockTV) AddPixelRefresher(_ television.PixelRefresher) {
}
func (t *mockTV) AddFrameTrigger(_ television.FrameTrigger) {
}
func (t *mockTV) AddAudioMixer(_ television.AudioMixer) {
}
func (t *mockTV) Reset() error {
return nil
}
func (t *mockTV) End() error {
return nil
}
func (t *mockTV) Signal(_ television.SignalAttributes) error {
return nil
}
func (t *mockTV) IsStable() bool {
return true
}
func (t *mockTV) GetLastSignal() television.SignalAttributes {
return television.SignalAttributes{}
}
func (t *mockTV) GetState(_ television.StateReq) (int, error) {
return 0, nil
}
func (t *mockTV) SetSpec(_ string) error {
return nil
}
func (t *mockTV) GetReqSpecID() string {
return ""
}
func (t *mockTV) GetSpec() television.Spec {
return television.SpecNTSC
}
func (t *mockTV) SetFPSCap(set bool) {
}
func (t *mockTV) SetFPS(fps float32) {
}
func (t *mockTV) GetReqFPS() float32 {
return 0.0
}
func (t *mockTV) GetActualFPS() float32 {
return 0.0
}
type mockGUI struct{}
func (g *mockGUI) ReqFeature(request gui.FeatureReq, args ...interface{}) error {
@ -212,7 +137,12 @@ func (trm *mockTerm) testSequence() {
func TestDebugger_withNonExistantInitScript(t *testing.T) {
trm := newMockTerm(t)
dbg, err := debugger.NewDebugger(&mockTV{}, &mockGUI{}, trm, false)
tv, err := television.NewTelevision("NTSC")
if err != nil {
t.Fatalf(err.Error())
}
dbg, err := debugger.NewDebugger(tv, &mockGUI{}, trm, false)
if err != nil {
t.Fatalf(err.Error())
}
@ -227,8 +157,12 @@ func TestDebugger_withNonExistantInitScript(t *testing.T) {
func TestDebugger(t *testing.T) {
trm := newMockTerm(t)
tv, err := television.NewTelevision("NTSC")
if err != nil {
t.Fatalf(err.Error())
}
dbg, err := debugger.NewDebugger(&mockTV{}, &mockGUI{}, trm, false)
dbg, err := debugger.NewDebugger(tv, &mockGUI{}, trm, false)
if err != nil {
t.Fatalf(err.Error())
}

View file

@ -58,7 +58,7 @@ const (
)
type limiter struct {
tv television.Television
tv *television.Television
lmtr *time.Ticker
throt throttleLevel
reqFrames float32
@ -70,7 +70,7 @@ type limiter struct {
checkEvents func() error
}
func newLimiter(tv television.Television, checkEvents func() error) *limiter {
func newLimiter(tv *television.Television, checkEvents func() error) *limiter {
tv.SetFPSCap(false)
lmtr := &limiter{

View file

@ -30,7 +30,7 @@ import (
// Note that the use of SHA-1 is fine for this application because this is not
// a cryptographic task.
type Video struct {
television.Television
*television.Television
spec television.Spec
digest [sha1.Size]byte
pixels []byte
@ -42,7 +42,7 @@ const pixelDepth = 3
// NewVideo initialises a new instance of DigestTV. For convenience, the
// television argument can be nil, in which case an instance of
// StellaTelevision will be created.
func NewVideo(tv television.Television) (*Video, error) {
func NewVideo(tv *television.Television) (*Video, error) {
// set up digest tv
dig := &Video{Television: tv}

View file

@ -290,7 +290,7 @@ func play(md *modalflag.Modes, sync *mainSync) error {
case 1:
cartload := cartridgeloader.NewLoader(md.GetArg(0), *mapping)
tv, err := television.NewReference(*spec)
tv, err := television.NewTelevision(*spec)
if err != nil {
return err
}
@ -389,7 +389,7 @@ func debug(md *modalflag.Modes, sync *mainSync) error {
return err
}
tv, err := television.NewReference(*spec)
tv, err := television.NewTelevision(*spec)
if err != nil {
return err
}
@ -569,7 +569,7 @@ func perform(md *modalflag.Modes, sync *mainSync) error {
case 1:
cartload := cartridgeloader.NewLoader(md.GetArg(0), *mapping)
tv, err := television.NewReference(*spec)
tv, err := television.NewTelevision(*spec)
if err != nil {
return err
}

View file

@ -28,7 +28,7 @@ import (
// SdlDebug is a simple SDL implementation of the television.PixelRenderer interfac.
type SdlDebug struct {
television.Television
*television.Television
// functions that need to be performed in the main thread should be queued
// for service
@ -98,7 +98,7 @@ const windowTitle = "Gopher2600"
const windowTitleCaptured = "Gopher2600 [captured]"
// NewSdlDebug is the preferred method of initialisation for SdlDebug.
func NewSdlDebug(tv television.Television, scale float32) (*SdlDebug, error) {
func NewSdlDebug(tv *television.Television, scale float32) (*SdlDebug, error) {
scr := &SdlDebug{
Television: tv,
service: make(chan func(), 1),

View file

@ -47,7 +47,7 @@ type SdlImgui struct {
// references to the emulation
lz *lazyvalues.LazyValues
tv television.Television
tv *television.Television
vcs *hardware.VCS
// terminal interface to the debugger
@ -97,7 +97,7 @@ type SdlImgui struct {
// NewSdlImgui is the preferred method of initialisation for type SdlImgui
//
// MUST ONLY be called from the #mainthread.
func NewSdlImgui(tv television.Television, playmode bool) (*SdlImgui, error) {
func NewSdlImgui(tv *television.Television, playmode bool) (*SdlImgui, error) {
img := &SdlImgui{
context: imgui.CreateContext(nil),
io: imgui.CurrentIO(),

View file

@ -0,0 +1,181 @@
// This file is part of Gopher2600.
//
// Gopher2600 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 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 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 Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package television
import "strings"
// TelevisionTIA exposes only the functions required by the TIA.
type TelevisionTIA interface {
Signal(SignalAttributes) error
GetState(StateReq) (int, error)
}
// TelevisionSprite exposes only the functions required by the video sprites.
type TelevisionSprite interface {
GetState(StateReq) (int, error)
}
// TelevisionState is a deliberately opaque type returned by Snapshot() and
// used by RestoreSnapshot() in the Television interface. The state itself can
// consist of anything necessary to the Television implementation.
type TelevisionState interface{}
// PixelRenderer implementations displays, or otherwise works with, visual
// information from a television. For example digest.Video.
//
// PixelRenderer implementations often find it convenient to maintain a reference to
// the parent Television implementation and maybe even embed the Television
// interface. ie.
//
// type ExampleTV struct {
// television.Television
// ...
// }
type PixelRenderer interface {
// Resize is called when the television implementation detects that extra
// scanlines are required in the display.
//
// It may be called when television specification has changed. As a point
// of convenience a reference to the currently selected specification is
// provided. However, renderers should call GetSpec() rather than keeping a
// private pointer to the specification, if knowledge of the spec is
// required after the Resize() event.
//
// Renderers should use the values sent by the Resize() function, rather
// than the equivalent values in the specification. Unless of course, the
// renderer is intended to be strict about specification accuracy.
//
// Renderers should make sure that any data structures that depend on the
// specification being used are still adequate.
Resize(spec Spec, topScanline, visibleScanlines int) error
// NewFrame and NewScanline are called at the start of the frame/scanline
NewFrame(frameNum int, isStable bool) error
NewScanline(scanline int) error
// setPixel() is called every cycle regardless of the state of VBLANK and
// HBLANK.
//
// things to consider:
//
// o the x argument is measured from zero so renderers should decide how to
// handle pixels of during the HBLANK (x < ClocksPerHBLANK)
//
// o the y argument is also measured from zero but because VBLANK can be
// turned on at any time there's no easy test. the VBLANK flag is sent to
// help renderers decide what to do.
//
// o for renderers that are producing an accurate visual image, the pixel
// should always be set to video black if VBLANK is on.
//
// some renderers however, may find it useful to set the pixel to the RGB
// value regardless of VBLANK. for example, DigestTV does this.
//
// a vey important note is that some ROMs use VBLANK to control pixel
// color within the visible display area. ROMs affected:
//
// * Custer's Revenge
// * Ladybug
// * ET (turns VBLANK off late on scanline 40)
//
// Set refreshing flag to true when called between Refresh(true) and
// Refresh(false)
SetPixel(x, y int, red, green, blue byte, vblank bool) error
// some renderers may need to conclude and/or dispose of resources gently.
// for simplicity, the PixelRenderer should be considered unusable after
// EndRendering() has been called
EndRendering() error
}
// PixelRefresher implementations are prepared to accept pixels outside of the
// normal PixelRenderer sequence.
type PixelRefresher interface {
// Mark the start and end of a refresh event from the television. These
// events occur when the television wants to dump many pixels at once.
// Use SetPixel() with a refreshing flag of true between calls to
// Refresh(true) and Refresh(false)
Refresh(refreshing bool)
// RefreshPixel should only be called between two call of Refresh() as
// described above
RefreshPixel(x, y int, red, green, blue byte, vblank bool, stale bool) error
}
// FrameTrigger implementations listen for NewFrame events. FrameTrigger is a
// subset of PixelRenderer.
type FrameTrigger interface {
NewFrame(frameNum int, isStable bool) error
}
// AudioMixer implementations work with sound; most probably playing it. An
// example of an AudioMixer that does not play sound but otherwise works with
// it is the digest.Audio type.
type AudioMixer interface {
SetAudio(audioData uint8) error
// some mixers may need to conclude and/or dispose of resources gently.
// for simplicity, the AudioMixer should be considered unusable after
// EndMixing() has been called
EndMixing() error
}
// ColorSignal represents the signal that is sent from the VCS to the.
type ColorSignal int
// VideoBlack is the PixelSignal value that indicates no VCS pixel is to be shown.
const VideoBlack ColorSignal = -1
// SignalAttributes represents the data sent to the television.
type SignalAttributes struct {
VSync bool
VBlank bool
CBurst bool
HSync bool
Pixel ColorSignal
AudioData uint8
// which equates to 30Khz
AudioUpdate bool
}
func (a SignalAttributes) String() string {
s := strings.Builder{}
if a.VSync {
s.WriteString("VSYNC ")
}
if a.VBlank {
s.WriteString("VBLANK ")
}
if a.CBurst {
s.WriteString("CBURST ")
}
if a.HSync {
s.WriteString("HSYNC ")
}
return s.String()
}
// StateReq is used to identify which television attribute is being asked
// with the GetState() function.
type StateReq int
// List of valid state requests.
const (
ReqFramenum StateReq = iota
ReqScanline
ReqHorizPos
)

View file

@ -1,511 +0,0 @@
// This file is part of Gopher2600.
//
// Gopher2600 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 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 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 Gopher2600. If not, see <https://www.gnu.org/licenses/>.
// ROMs used to test PAL switching and resizing:
// * Pitfall
// * Hero
// * Chiphead
// * Bang!
// * Ladybug
// * Hack Em Hangly Pacman
// * Andrew Davies' Chess
// * Communist Mutants From Space
// * Mega Bitmap Demo
package television
import (
"fmt"
"strings"
"github.com/jetsetilly/gopher2600/curated"
)
// the number of additional lines over the NTSC spec that is allowed before the
// TV flips to the PAL specification.
const excessScanlinesNTSC = 40
// the number of synced frames where we can expect things to be in flux.
const leadingFrames = 5
// the number of synced frames required before the tv frame is considered to "stable".
const stabilityThreshold = 20
type state struct {
// television specification (NTSC or PAL)
spec Spec
// auto flag indicates that the tv type/specification should switch if it
// appears to be outside of the current spec.
//
// in practice this means that if auto is true then we start with the NTSC
// spec and move to PAL if the number of scanlines exceeds the NTSC maximum
auto bool
// state of the television
// - the current horizontal position. the position where the next pixel will be
// drawn. also used to check we're receiving the correct signals at the
// correct time.
horizPos int
// - the current frame
frameNum int
// - the current scanline number
scanline int
// - the current synced frame number. a synced frame is one which was
// generated from a valid VSYNC/VBLANK sequence. we use this to detect:
// * whether the image is "stable"
// * whether specification changes should still occur
syncedFrameNum int
// is current frame as a result of a VSYNC flyback or not (a "natural"
// flyback). we use this in the context of newFrame() so we should probably
// think of this as the previous frame.
syncedFrame bool
// record of signal attributes from the last call to Signal()
lastSignal SignalAttributes
// vsyncCount records the number of consecutive colorClocks the vsync signal
// has been sustained. we use this to help correctly implement vsync.
vsyncCount int
// top and bottom of screen as detected by vblank/color signal
top int
bottom int
// list of signals sent to pixel renderers since the beginning of the
// current frame
signalHistory []signalHistoryEntry
// the index to write the next signal
signalHistoryIdx int
}
type signalHistoryEntry struct {
x int
y int
sig SignalAttributes
}
// reference is a reference implementation of the Television interface. In all
// honesty, it's most likely the only implementation required.
type reference struct {
// spec on creation ID is the string that was to ID the television
// type/spec on creation. because the actual spec can change, the ID field
// of the Spec type can not be used for things like regression
// test recreation etc.
reqSpecID string
// frame resizer
resizer resizer
// framerate limiter
lmtr limiter
// list of renderer implementations to consult
renderers []PixelRenderer
// list of refresher implementations to consult
refreshers []PixelRefresher
// list of frametrigger implementations to consult
frameTriggers []FrameTrigger
// list of audio mixers to consult
mixers []AudioMixer
state *state
}
// NewReference creates a new instance of the reference television type,
// satisfying the Television interface.
func NewReference(spec string) (Television, error) {
tv := &reference{
resizer: &simpleResizer{},
reqSpecID: strings.ToUpper(spec),
state: &state{},
}
// set specification
err := tv.SetSpec(spec)
if err != nil {
return nil, err
}
// initialise frame rate limiter
tv.lmtr.init()
tv.SetFPS(-1)
// empty list of renderers
tv.renderers = make([]PixelRenderer, 0)
return tv, nil
}
func (tv reference) String() string {
s := strings.Builder{}
s.WriteString(fmt.Sprintf("FR=%04d SL=%03d HP=%03d", tv.state.frameNum, tv.state.scanline, tv.state.horizPos-HorizClksHBlank))
return s.String()
}
// Snapshot implements the Television interface.
func (tv *reference) Snapshot() TelevisionState {
n := *tv.state
n.signalHistory = make([]signalHistoryEntry, len(tv.state.signalHistory))
copy(n.signalHistory, tv.state.signalHistory)
return &n
}
// Plumb implements the Television interface.
func (tv *reference) Plumb(s TelevisionState) {
if s == nil {
return
}
tv.state = s.(*state)
for _, r := range tv.refreshers {
r.Refresh(true)
}
for i, e := range tv.state.signalHistory {
col := tv.state.spec.getColor(e.sig.Pixel)
for _, r := range tv.refreshers {
r.RefreshPixel(e.x, e.y, col.R, col.G, col.B, e.sig.VBlank, i >= tv.state.signalHistoryIdx)
}
}
for _, r := range tv.refreshers {
r.Refresh(false)
}
}
// AddPixelRenderer implements the Television interface.
func (tv *reference) AddPixelRenderer(r PixelRenderer) {
tv.renderers = append(tv.renderers, r)
tv.frameTriggers = append(tv.frameTriggers, r)
}
// AddPixelRefresher implements the Television interface.
func (tv *reference) AddPixelRefresher(r PixelRefresher) {
tv.refreshers = append(tv.refreshers, r)
}
// AddFrameTrigger implements the Television interface.
func (tv *reference) AddFrameTrigger(f FrameTrigger) {
tv.frameTriggers = append(tv.frameTriggers, f)
}
// AddAudioMixer implements the Television interface.
func (tv *reference) AddAudioMixer(m AudioMixer) {
tv.mixers = append(tv.mixers, m)
}
// Reset implements the Television interface.
func (tv *reference) Reset() error {
// we definitely do not call this on television initialisation because the
// rest of the system may not be yet be in a suitable state
err := tv.SetSpec(tv.reqSpecID)
if err != nil {
return err
}
tv.state.horizPos = 0
tv.state.frameNum = 0
tv.state.scanline = 0
tv.state.syncedFrameNum = 0
tv.state.vsyncCount = 0
tv.state.lastSignal = SignalAttributes{}
return nil
}
// End implements the Television interface.
func (tv reference) End() error {
var err error
// call new frame for all renderers
for f := range tv.renderers {
err = tv.renderers[f].EndRendering()
}
// flush audio for all mixers
for f := range tv.mixers {
err = tv.mixers[f].EndMixing()
}
return err
}
// Signal implements the Television interface.
func (tv *reference) Signal(sig SignalAttributes) error {
// mix audio before we do anything else
if sig.AudioUpdate {
for f := range tv.mixers {
err := tv.mixers[f].SetAudio(sig.AudioData)
if err != nil {
return err
}
}
}
// examine signal for resizing possibility
tv.resizer.examine(tv, sig)
// a Signal() is by definition a new color clock. increase the horizontal count
tv.state.horizPos++
// once we reach the scanline's back-porch we'll reset the horizPos counter
// and wait for the HSYNC signal. we do this so that the front-porch and
// back-porch are 'together' at the beginning of the scanline. this isn't
// strictly technically correct but it's convenient to think about
// scanlines in this way (rather than having a split front and back porch)
if tv.state.horizPos >= HorizClksScanline {
tv.state.horizPos = 0
// bump scanline counter
tv.state.scanline++
// reached end of screen without synchronisation. fly-back naturally.
if tv.state.scanline > tv.state.spec.ScanlinesTotal {
err := tv.newFrame(false)
if err != nil {
return err
}
} else {
// if we're not at end of screen then indicate new scanline
err := tv.newScanline()
if err != nil {
return err
}
}
// checkRate evey scanline. see checkRate() commentary for why this is
tv.lmtr.checkRate()
}
// check vsync signal at the time of the flyback
//
// !!TODO: replace VSYNC signal with extended HSYNC signal
if sig.VSync && !tv.state.lastSignal.VSync {
tv.state.vsyncCount = 0
} else if !sig.VSync && tv.state.lastSignal.VSync {
if tv.state.vsyncCount > 0 {
err := tv.newFrame(true)
if err != nil {
return err
}
}
}
// we've "faked" the flyback signal above when horizPos reached
// horizClksScanline. we need to handle the real flyback signal however, by
// making sure we're at the correct horizPos value. if horizPos doesn't
// equal 16 at the front of the HSYNC or 36 at then back of the HSYNC, then
// it indicates that the RSYNC register was used last scanline.
if sig.HSync && !tv.state.lastSignal.HSync {
tv.state.horizPos = 16
// count vsync lines at start of hsync
if sig.VSync || tv.state.lastSignal.VSync {
tv.state.vsyncCount++
}
}
if !sig.HSync && tv.state.lastSignal.HSync {
tv.state.horizPos = 36
}
// doing nothing with CBURST signal
// decode color using the regular color signal
col := tv.state.spec.getColor(sig.Pixel)
for f := range tv.renderers {
err := tv.renderers[f].SetPixel(tv.state.horizPos, tv.state.scanline,
col.R, col.G, col.B, sig.VBlank)
if err != nil {
return err
}
}
// record the current signal settings so they can be used for reference
tv.state.lastSignal = sig
e := signalHistoryEntry{
x: tv.state.horizPos,
y: tv.state.scanline,
sig: sig,
}
if tv.state.signalHistoryIdx >= len(tv.state.signalHistory) {
tv.state.signalHistory = append(tv.state.signalHistory, e)
} else {
tv.state.signalHistory[tv.state.signalHistoryIdx] = e
}
tv.state.signalHistoryIdx++
return nil
}
func (tv *reference) newScanline() error {
// notify renderers of new scanline
for f := range tv.renderers {
err := tv.renderers[f].NewScanline(tv.state.scanline)
if err != nil {
return err
}
}
return nil
}
func (tv *reference) newFrame(synced bool) error {
// a synced frame is one which was generated from a valid VSYNC/VBLANK sequence
if tv.state.syncedFrame {
tv.state.syncedFrameNum++
}
// specification change
if tv.state.syncedFrameNum > leadingFrames && tv.state.syncedFrameNum < stabilityThreshold {
if tv.state.auto && !tv.state.syncedFrame && tv.state.scanline > excessScanlinesNTSC {
// flip from NTSC to PAL
if tv.state.spec.ID == SpecNTSC.ID {
_ = tv.SetSpec("PAL")
}
}
}
// commit any resizing that maybe pending
err := tv.resizer.commit(tv)
if err != nil {
return err
}
// prepare for next frame
tv.state.frameNum++
tv.state.scanline = 0
tv.resizer.prepare(tv)
tv.state.syncedFrame = synced
// process all FrameTriggers
for f := range tv.frameTriggers {
err = tv.frameTriggers[f].NewFrame(tv.state.frameNum, tv.IsStable())
if err != nil {
return err
}
}
// reset signal history for next frame
tv.state.signalHistoryIdx = 0
return nil
}
// IsStable implements the Television interface.
func (tv reference) IsStable() bool {
return tv.state.syncedFrameNum >= stabilityThreshold
}
// GetLastSignal implements the Television interface.
func (tv *reference) GetLastSignal() SignalAttributes {
return tv.state.lastSignal
}
// GetState implements the Television interface.
func (tv *reference) GetState(request StateReq) (int, error) {
switch request {
case ReqFramenum:
return tv.state.frameNum, nil
case ReqScanline:
return tv.state.scanline, nil
case ReqHorizPos:
return tv.state.horizPos - HorizClksHBlank, nil
default:
return 0, curated.Errorf("television: unhandled tv state request (%v)", request)
}
}
// SetSpec implements the Television interface.
func (tv *reference) SetSpec(spec string) error {
switch strings.ToUpper(spec) {
case "NTSC":
tv.state.spec = SpecNTSC
tv.state.auto = false
case "PAL":
tv.state.spec = SpecPAL
tv.state.auto = false
case "AUTO":
tv.state.spec = SpecNTSC
tv.state.auto = true
default:
return curated.Errorf("television: unsupported spec (%s)", spec)
}
tv.state.top = tv.state.spec.ScanlineTop
tv.state.bottom = tv.state.spec.ScanlineBottom
tv.resizer.prepare(tv)
for f := range tv.renderers {
err := tv.renderers[f].Resize(tv.state.spec, tv.state.top, tv.state.bottom-tv.state.top)
if err != nil {
return err
}
}
// allocate enough memory for a TV screen that stays within the limits of
// the specification
tv.state.signalHistory = make([]signalHistoryEntry, HorizClksScanline*(tv.state.bottom-tv.state.top))
tv.state.signalHistoryIdx = 0
return nil
}
// GetReqSpecID implements the Television interface.
func (tv *reference) GetReqSpecID() string {
return tv.reqSpecID
}
// GetSpec implements the Television interface.
func (tv reference) GetSpec() Spec {
return tv.state.spec
}
// SetFPSCap implements the Television interface. Reasons for turning the cap
// off include performance measurement. The debugger also turns the cap off and
// replaces it with its own. The FPS limiter in this television implementation
// works at the frame level which is not fine grained enough for effective
// limiting of rates less than 1fps.
func (tv *reference) SetFPSCap(limit bool) {
tv.lmtr.limit = limit
}
// SetFPS implements the Television interface. A negative value resets the FPS
// to the specification's ideal value.
func (tv *reference) SetFPS(fps float32) {
if fps == -1 {
fps = tv.state.spec.FramesPerSecond
}
tv.lmtr.setRate(fps, tv.state.spec.ScanlinesTotal)
}
// GetReqFPS implements the Television interface.
func (tv *reference) GetReqFPS() float32 {
return tv.lmtr.requested
}
// GetActualFPS implements the Television interface. Note that FPS measurement
// still works even when frame capping is disabled.
func (tv *reference) GetActualFPS() float32 {
return tv.lmtr.actual
}

View file

@ -33,13 +33,13 @@ type resizer interface {
id() FrameResizeID
// examine signal for resizing possibility. called on every Signal()
examine(tv *reference, sig SignalAttributes)
examine(tv *Television, sig SignalAttributes)
// commit resizing possiblity. called on every newFrame()
commit(tv *reference) error
commit(tv *Television) error
// preapare for next frame
prepare(tv *reference)
prepare(tv *Television)
}
// simpleResizer is the simplest functional and non-trivial implementation of
@ -52,7 +52,7 @@ func (sr simpleResizer) id() FrameResizeID {
return FrameResizerSimple
}
func (sr *simpleResizer) examine(tv *reference, sig SignalAttributes) {
func (sr *simpleResizer) examine(tv *Television, sig SignalAttributes) {
// if vblank is off at any point of then extend the bottom of the screen.
// we'll commit the resize procedure in the newFrame() function
//
@ -74,7 +74,7 @@ func (sr *simpleResizer) examine(tv *reference, sig SignalAttributes) {
}
}
func (sr *simpleResizer) commit(tv *reference) error {
func (sr *simpleResizer) commit(tv *Television) error {
// always perform resize operation
if tv.state.syncedFrameNum <= leadingFrames || sr.bottom == tv.state.bottom {
return nil
@ -104,6 +104,6 @@ func (sr *simpleResizer) commit(tv *reference) error {
return nil
}
func (sr *simpleResizer) prepare(tv *reference) {
func (sr *simpleResizer) prepare(tv *Television) {
sr.bottom = tv.state.bottom
}

View file

@ -13,252 +13,513 @@
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
// ROMs used to test PAL switching and resizing:
// * Pitfall
// * Hero
// * Chiphead
// * Bang!
// * Ladybug
// * Hack Em Hangly Pacman
// * Andrew Davies' Chess
// * Communist Mutants From Space
// * Mega Bitmap Demo
package television
import "strings"
import (
"fmt"
"strings"
// Television defines the operations that can be performed on the conceptual
// television. Note that the television implementation itself does not present
// any information, either visually or sonically. Instead, PixelRenderers and
// AudioMixers are added to perform those tasks.
type Television interface {
String() string
"github.com/jetsetilly/gopher2600/curated"
)
// Make a copy of the television state
Snapshot() TelevisionState
// the number of additional lines over the NTSC spec that is allowed before the
// TV flips to the PAL specification.
const excessScanlinesNTSC = 40
// Copy back television state
Plumb(TelevisionState)
// the number of synced frames where we can expect things to be in flux.
const leadingFrames = 5
// AddPixelRenderer registers an implementation of PixelRenderer. Multiple
// implemntations can be added
AddPixelRenderer(PixelRenderer)
// the number of synced frames required before the tv frame is considered to "stable".
const stabilityThreshold = 20
// AddPixelRefresh registers an implementation of PixelRefresher. Multiple
// implemntations can be added
AddPixelRefresher(PixelRefresher)
type state struct {
// television specification (NTSC or PAL)
spec Spec
// AddFrameTrigger registers an implementation of FrameTrigger. Multiple
// implemntations can be added
AddFrameTrigger(FrameTrigger)
// AddAudioMixer registers an implementation of AudioMixer. Multiple
// implemntations can be added
AddAudioMixer(AudioMixer)
// Reset the television to an initial state
Reset() error
// some televisions may need to conclude and/or dispose of resources
// gently. implementations of End() should call EndRendering() and
// EndMixing() on each PixelRenderer and AudioMixer that has been added.
// auto flag indicates that the tv type/specification should switch if it
// appears to be outside of the current spec.
//
// for simplicity, the Television should be considered unusable
// after EndRendering() has been called
End() error
// in practice this means that if auto is true then we start with the NTSC
// spec and move to PAL if the number of scanlines exceeds the NTSC maximum
auto bool
Signal(SignalAttributes) error
// state of the television
// - the current horizontal position. the position where the next pixel will be
// drawn. also used to check we're receiving the correct signals at the
// correct time.
horizPos int
// - the current frame
frameNum int
// - the current scanline number
scanline int
// - the current synced frame number. a synced frame is one which was
// generated from a valid VSYNC/VBLANK sequence. we use this to detect:
// * whether the image is "stable"
// * whether specification changes should still occur
syncedFrameNum int
// \/\/ reflection/information \/\/
// is current frame as a result of a VSYNC flyback or not (a "natural"
// flyback). we use this in the context of newFrame() so we should probably
// think of this as the previous frame.
syncedFrame bool
// IsStable returns true if the television thinks the image being sent by
// the VCS is stable
IsStable() bool
// record of signal attributes from the last call to Signal()
lastSignal SignalAttributes
// Returns a copy of SignalAttributes for reference
GetLastSignal() SignalAttributes
// vsyncCount records the number of consecutive colorClocks the vsync signal
// has been sustained. we use this to help correctly implement vsync.
vsyncCount int
// Returns state information
GetState(StateReq) (int, error)
// top and bottom of screen as detected by vblank/color signal
top int
bottom int
// \/\/ specfication \/\/
//
// Set the television's specification
SetSpec(spec string) error
// list of signals sent to pixel renderers since the beginning of the
// current frame
signalHistory []signalHistoryEntry
// GetReqSpecID returns the specification that was requested on creation
GetReqSpecID() string
// Returns the television's current specification. Renderers should use
// GetSpec() rather than keeping a private pointer to the specification.
GetSpec() Spec
// \/\/ FPS \/\/
// Set whether the emulation should wait for FPS limiter
SetFPSCap(set bool)
// Request the number frames per second. This overrides the frame rate of
// the specification. A negative value restores the spec's frame rate.
SetFPS(fps float32)
// The requested number of frames per second. Compare with GetActualFPS()
// to check for accuracy
GetReqFPS() float32
// The current number of frames per second
GetActualFPS() float32
// the index to write the next signal
signalHistoryIdx int
}
// TelevisionTIA exposes only the functions required by the TIA.
type TelevisionTIA interface {
Signal(SignalAttributes) error
GetState(StateReq) (int, error)
type signalHistoryEntry struct {
x int
y int
sig SignalAttributes
}
// TelevisionSprite exposes only the functions required by the video sprites.
type TelevisionSprite interface {
GetState(StateReq) (int, error)
// Television is a Television implementation of the Television interface. In all
// honesty, it's most likely the only implementation required.
type Television struct {
// spec on creation ID is the string that was to ID the television
// type/spec on creation. because the actual spec can change, the ID field
// of the Spec type can not be used for things like regression
// test recreation etc.
reqSpecID string
// frame resizer
resizer resizer
// framerate limiter
lmtr limiter
// list of renderer implementations to consult
renderers []PixelRenderer
// list of refresher implementations to consult
refreshers []PixelRefresher
// list of frametrigger implementations to consult
frameTriggers []FrameTrigger
// list of audio mixers to consult
mixers []AudioMixer
state *state
}
// TelevisionState is a deliberately opaque type returned by Snapshot() and
// used by RestoreSnapshot() in the Television interface. The state itself can
// consist of anything necessary to the Television implementation.
type TelevisionState interface{}
// NewReference creates a new instance of the reference television type,
// satisfying the Television interface.
func NewTelevision(spec string) (*Television, error) {
tv := &Television{
resizer: &simpleResizer{},
reqSpecID: strings.ToUpper(spec),
state: &state{},
}
// PixelRenderer implementations displays, or otherwise works with, visual
// information from a television. For example digest.Video.
//
// PixelRenderer implementations often find it convenient to maintain a reference to
// the parent Television implementation and maybe even embed the Television
// interface. ie.
//
// type ExampleTV struct {
// television.Television
// ...
// }
type PixelRenderer interface {
// Resize is called when the television implementation detects that extra
// scanlines are required in the display.
//
// It may be called when television specification has changed. As a point
// of convenience a reference to the currently selected specification is
// provided. However, renderers should call GetSpec() rather than keeping a
// private pointer to the specification, if knowledge of the spec is
// required after the Resize() event.
//
// Renderers should use the values sent by the Resize() function, rather
// than the equivalent values in the specification. Unless of course, the
// renderer is intended to be strict about specification accuracy.
//
// Renderers should make sure that any data structures that depend on the
// specification being used are still adequate.
Resize(spec Spec, topScanline, visibleScanlines int) error
// set specification
err := tv.SetSpec(spec)
if err != nil {
return nil, err
}
// NewFrame and NewScanline are called at the start of the frame/scanline
NewFrame(frameNum int, isStable bool) error
NewScanline(scanline int) error
// initialise frame rate limiter
tv.lmtr.init()
tv.SetFPS(-1)
// setPixel() is called every cycle regardless of the state of VBLANK and
// HBLANK.
//
// things to consider:
//
// o the x argument is measured from zero so renderers should decide how to
// handle pixels of during the HBLANK (x < ClocksPerHBLANK)
//
// o the y argument is also measured from zero but because VBLANK can be
// turned on at any time there's no easy test. the VBLANK flag is sent to
// help renderers decide what to do.
//
// o for renderers that are producing an accurate visual image, the pixel
// should always be set to video black if VBLANK is on.
//
// some renderers however, may find it useful to set the pixel to the RGB
// value regardless of VBLANK. for example, DigestTV does this.
//
// a vey important note is that some ROMs use VBLANK to control pixel
// color within the visible display area. ROMs affected:
//
// * Custer's Revenge
// * Ladybug
// * ET (turns VBLANK off late on scanline 40)
//
// Set refreshing flag to true when called between Refresh(true) and
// Refresh(false)
SetPixel(x, y int, red, green, blue byte, vblank bool) error
// empty list of renderers
tv.renderers = make([]PixelRenderer, 0)
// some renderers may need to conclude and/or dispose of resources gently.
// for simplicity, the PixelRenderer should be considered unusable after
// EndRendering() has been called
EndRendering() error
return tv, nil
}
// PixelRefresher implementations are prepared to accept pixels outside of the
// normal PixelRenderer sequence.
type PixelRefresher interface {
// Mark the start and end of a refresh event from the television. These
// events occur when the television wants to dump many pixels at once.
// Use SetPixel() with a refreshing flag of true between calls to
// Refresh(true) and Refresh(false)
Refresh(refreshing bool)
// RefreshPixel should only be called between two call of Refresh() as
// described above
RefreshPixel(x, y int, red, green, blue byte, vblank bool, stale bool) error
}
// FrameTrigger implementations listen for NewFrame events. FrameTrigger is a
// subset of PixelRenderer.
type FrameTrigger interface {
NewFrame(frameNum int, isStable bool) error
}
// AudioMixer implementations work with sound; most probably playing it. An
// example of an AudioMixer that does not play sound but otherwise works with
// it is the digest.Audio type.
type AudioMixer interface {
SetAudio(audioData uint8) error
// some mixers may need to conclude and/or dispose of resources gently.
// for simplicity, the AudioMixer should be considered unusable after
// EndMixing() has been called
EndMixing() error
}
// ColorSignal represents the signal that is sent from the VCS to the.
type ColorSignal int
// VideoBlack is the PixelSignal value that indicates no VCS pixel is to be shown.
const VideoBlack ColorSignal = -1
// SignalAttributes represents the data sent to the television.
type SignalAttributes struct {
VSync bool
VBlank bool
CBurst bool
HSync bool
Pixel ColorSignal
AudioData uint8
// which equates to 30Khz
AudioUpdate bool
}
func (a SignalAttributes) String() string {
func (tv Television) String() string {
s := strings.Builder{}
if a.VSync {
s.WriteString("VSYNC ")
}
if a.VBlank {
s.WriteString("VBLANK ")
}
if a.CBurst {
s.WriteString("CBURST ")
}
if a.HSync {
s.WriteString("HSYNC ")
}
s.WriteString(fmt.Sprintf("FR=%04d SL=%03d HP=%03d", tv.state.frameNum, tv.state.scanline, tv.state.horizPos-HorizClksHBlank))
return s.String()
}
// StateReq is used to identify which television attribute is being asked
// with the GetState() function.
type StateReq int
// Snapshot makes a copy of the television state.
func (tv *Television) Snapshot() TelevisionState {
n := *tv.state
n.signalHistory = make([]signalHistoryEntry, len(tv.state.signalHistory))
copy(n.signalHistory, tv.state.signalHistory)
return &n
}
// List of valid state requests.
const (
ReqFramenum StateReq = iota
ReqScanline
ReqHorizPos
)
// Plumb in an existing television state.
func (tv *Television) Plumb(s TelevisionState) {
if s == nil {
return
}
tv.state = s.(*state)
for _, r := range tv.refreshers {
r.Refresh(true)
}
for i, e := range tv.state.signalHistory {
col := tv.state.spec.getColor(e.sig.Pixel)
for _, r := range tv.refreshers {
r.RefreshPixel(e.x, e.y, col.R, col.G, col.B, e.sig.VBlank, i >= tv.state.signalHistoryIdx)
}
}
for _, r := range tv.refreshers {
r.Refresh(false)
}
}
// AddPixelRenderer registers an implementation of PixelRenderer. Multiple
// implemntations can be added.
func (tv *Television) AddPixelRenderer(r PixelRenderer) {
tv.renderers = append(tv.renderers, r)
tv.frameTriggers = append(tv.frameTriggers, r)
}
// AddPixelRefresh registers an implementation of PixelRefresher. Multiple
// implemntations can be added.
func (tv *Television) AddPixelRefresher(r PixelRefresher) {
tv.refreshers = append(tv.refreshers, r)
}
// AddFrameTrigger registers an implementation of FrameTrigger. Multiple
// implemntations can be added.
func (tv *Television) AddFrameTrigger(f FrameTrigger) {
tv.frameTriggers = append(tv.frameTriggers, f)
}
// AddAudioMixer registers an implementation of AudioMixer. Multiple
// implemntations can be added.
func (tv *Television) AddAudioMixer(m AudioMixer) {
tv.mixers = append(tv.mixers, m)
}
// Reset the television to an initial state.
func (tv *Television) Reset() error {
// we definitely do not call this on television initialisation because the
// rest of the system may not be yet be in a suitable state
err := tv.SetSpec(tv.reqSpecID)
if err != nil {
return err
}
tv.state.horizPos = 0
tv.state.frameNum = 0
tv.state.scanline = 0
tv.state.syncedFrameNum = 0
tv.state.vsyncCount = 0
tv.state.lastSignal = SignalAttributes{}
return nil
}
// some televisions may need to conclude and/or dispose of resources
// gently. implementations of End() should call EndRendering() and
// EndMixing() on each PixelRenderer and AudioMixer that has been added.
//
// for simplicity, the Television should be considered unusable
// after EndRendering() has been called.
func (tv Television) End() error {
var err error
// call new frame for all renderers
for f := range tv.renderers {
err = tv.renderers[f].EndRendering()
}
// flush audio for all mixers
for f := range tv.mixers {
err = tv.mixers[f].EndMixing()
}
return err
}
// Signal updates the current state of the television.
func (tv *Television) Signal(sig SignalAttributes) error {
// mix audio before we do anything else
if sig.AudioUpdate {
for f := range tv.mixers {
err := tv.mixers[f].SetAudio(sig.AudioData)
if err != nil {
return err
}
}
}
// examine signal for resizing possibility
tv.resizer.examine(tv, sig)
// a Signal() is by definition a new color clock. increase the horizontal count
tv.state.horizPos++
// once we reach the scanline's back-porch we'll reset the horizPos counter
// and wait for the HSYNC signal. we do this so that the front-porch and
// back-porch are 'together' at the beginning of the scanline. this isn't
// strictly technically correct but it's convenient to think about
// scanlines in this way (rather than having a split front and back porch)
if tv.state.horizPos >= HorizClksScanline {
tv.state.horizPos = 0
// bump scanline counter
tv.state.scanline++
// reached end of screen without synchronisation. fly-back naturally.
if tv.state.scanline > tv.state.spec.ScanlinesTotal {
err := tv.newFrame(false)
if err != nil {
return err
}
} else {
// if we're not at end of screen then indicate new scanline
err := tv.newScanline()
if err != nil {
return err
}
}
// checkRate evey scanline. see checkRate() commentary for why this is
tv.lmtr.checkRate()
}
// check vsync signal at the time of the flyback
//
// !!TODO: replace VSYNC signal with extended HSYNC signal
if sig.VSync && !tv.state.lastSignal.VSync {
tv.state.vsyncCount = 0
} else if !sig.VSync && tv.state.lastSignal.VSync {
if tv.state.vsyncCount > 0 {
err := tv.newFrame(true)
if err != nil {
return err
}
}
}
// we've "faked" the flyback signal above when horizPos reached
// horizClksScanline. we need to handle the real flyback signal however, by
// making sure we're at the correct horizPos value. if horizPos doesn't
// equal 16 at the front of the HSYNC or 36 at then back of the HSYNC, then
// it indicates that the RSYNC register was used last scanline.
if sig.HSync && !tv.state.lastSignal.HSync {
tv.state.horizPos = 16
// count vsync lines at start of hsync
if sig.VSync || tv.state.lastSignal.VSync {
tv.state.vsyncCount++
}
}
if !sig.HSync && tv.state.lastSignal.HSync {
tv.state.horizPos = 36
}
// doing nothing with CBURST signal
// decode color using the regular color signal
col := tv.state.spec.getColor(sig.Pixel)
for f := range tv.renderers {
err := tv.renderers[f].SetPixel(tv.state.horizPos, tv.state.scanline,
col.R, col.G, col.B, sig.VBlank)
if err != nil {
return err
}
}
// record the current signal settings so they can be used for reference
tv.state.lastSignal = sig
e := signalHistoryEntry{
x: tv.state.horizPos,
y: tv.state.scanline,
sig: sig,
}
if tv.state.signalHistoryIdx >= len(tv.state.signalHistory) {
tv.state.signalHistory = append(tv.state.signalHistory, e)
} else {
tv.state.signalHistory[tv.state.signalHistoryIdx] = e
}
tv.state.signalHistoryIdx++
return nil
}
func (tv *Television) newScanline() error {
// notify renderers of new scanline
for f := range tv.renderers {
err := tv.renderers[f].NewScanline(tv.state.scanline)
if err != nil {
return err
}
}
return nil
}
func (tv *Television) newFrame(synced bool) error {
// a synced frame is one which was generated from a valid VSYNC/VBLANK sequence
if tv.state.syncedFrame {
tv.state.syncedFrameNum++
}
// specification change
if tv.state.syncedFrameNum > leadingFrames && tv.state.syncedFrameNum < stabilityThreshold {
if tv.state.auto && !tv.state.syncedFrame && tv.state.scanline > excessScanlinesNTSC {
// flip from NTSC to PAL
if tv.state.spec.ID == SpecNTSC.ID {
_ = tv.SetSpec("PAL")
}
}
}
// commit any resizing that maybe pending
err := tv.resizer.commit(tv)
if err != nil {
return err
}
// prepare for next frame
tv.state.frameNum++
tv.state.scanline = 0
tv.resizer.prepare(tv)
tv.state.syncedFrame = synced
// process all FrameTriggers
for f := range tv.frameTriggers {
err = tv.frameTriggers[f].NewFrame(tv.state.frameNum, tv.IsStable())
if err != nil {
return err
}
}
// reset signal history for next frame
tv.state.signalHistoryIdx = 0
return nil
}
// IsStable returns true if the television thinks the image being sent by
// the VCS is stable.
func (tv Television) IsStable() bool {
return tv.state.syncedFrameNum >= stabilityThreshold
}
// Returns a copy of SignalAttributes for reference.
func (tv *Television) GetLastSignal() SignalAttributes {
return tv.state.lastSignal
}
// Returns state information.
func (tv *Television) GetState(request StateReq) (int, error) {
switch request {
case ReqFramenum:
return tv.state.frameNum, nil
case ReqScanline:
return tv.state.scanline, nil
case ReqHorizPos:
return tv.state.horizPos - HorizClksHBlank, nil
default:
return 0, curated.Errorf("television: unhandled tv state request (%v)", request)
}
}
// Set the television's specification.
func (tv *Television) SetSpec(spec string) error {
switch strings.ToUpper(spec) {
case "NTSC":
tv.state.spec = SpecNTSC
tv.state.auto = false
case "PAL":
tv.state.spec = SpecPAL
tv.state.auto = false
case "AUTO":
tv.state.spec = SpecNTSC
tv.state.auto = true
default:
return curated.Errorf("television: unsupported spec (%s)", spec)
}
tv.state.top = tv.state.spec.ScanlineTop
tv.state.bottom = tv.state.spec.ScanlineBottom
tv.resizer.prepare(tv)
for f := range tv.renderers {
err := tv.renderers[f].Resize(tv.state.spec, tv.state.top, tv.state.bottom-tv.state.top)
if err != nil {
return err
}
}
// allocate enough memory for a TV screen that stays within the limits of
// the specification
tv.state.signalHistory = make([]signalHistoryEntry, HorizClksScanline*(tv.state.bottom-tv.state.top))
tv.state.signalHistoryIdx = 0
return nil
}
// GetReqSpecID returns the specification that was requested on creation.
func (tv *Television) GetReqSpecID() string {
return tv.reqSpecID
}
// Returns the television's current specification. Renderers should use
// GetSpec() rather than keeping a private pointer to the specification.
func (tv Television) GetSpec() Spec {
return tv.state.spec
}
// SetFPSCap whether the emulation should wait for FPS limiter.
//
// Reasons for turning the cap
// off include performance measurement. The debugger also turns the cap off and
// replaces it with its own. The FPS limiter in this television implementation
// works at the frame level which is not fine grained enough for effective
// limiting of rates less than 1fps.
func (tv *Television) SetFPSCap(limit bool) {
tv.lmtr.limit = limit
}
// Request the number frames per second. This overrides the frame rate of
// the specification. A negative value restores the spec's frame rate.
func (tv *Television) SetFPS(fps float32) {
if fps == -1 {
fps = tv.state.spec.FramesPerSecond
}
tv.lmtr.setRate(fps, tv.state.spec.ScanlinesTotal)
}
// The requested number of frames per second. Compare with GetActualFPS()
// to check for accuracy.
func (tv *Television) GetReqFPS() float32 {
return tv.lmtr.requested
}
// The current number of frames per second. Note that FPS measurement still
// works even when frame capping is disabled.
func (tv *Television) GetActualFPS() float32 {
return tv.lmtr.actual
}

View file

@ -22,25 +22,22 @@ import (
)
func TestNewTelevision(t *testing.T) {
var tv television.Television
var err error
tv, err = television.NewReference("PAL")
tv, err := television.NewTelevision("PAL")
if tv == nil || err != nil {
t.Errorf("PAL spec creation failed")
}
tv, err = television.NewReference("NTSC")
tv, err = television.NewTelevision("NTSC")
if tv == nil || err != nil {
t.Errorf("NTSC spec creation failed")
}
tv, err = television.NewReference("AUTO")
tv, err = television.NewTelevision("AUTO")
if tv == nil || err != nil {
t.Errorf("AUTO spec creation failed")
}
tv, err = television.NewReference("FOO")
tv, err = television.NewTelevision("FOO")
if tv != nil || err == nil {
t.Errorf("'FOO' spec creation unexpectedly succeeded")
}

View file

@ -33,7 +33,7 @@ import (
// VCS struct is the main container for the emulated components of the VCS.
type VCS struct {
Prefs *preferences.Preferences
TV television.Television
TV *television.Television
// references to the different components of the VCS. do not take copies of
// these pointer values because the rewind feature will change them.
@ -47,7 +47,7 @@ type VCS struct {
// NewVCS creates a new VCS and everything associated with the hardware. It is
// used for all aspects of emulation: debugging sessions, and regular play.
func NewVCS(tv television.Television) (*VCS, error) {
func NewVCS(tv *television.Television) (*VCS, error) {
// set up preferences
prefs, err := preferences.NewPreferences()
if err != nil {

View file

@ -19,7 +19,7 @@ import "github.com/jetsetilly/gopher2600/hardware/television"
// CalcFPS takes the the number of frames and duration (in seconds) and returns
// the frames-per-second and the accuracy of that value as a percentage.
func CalcFPS(tv television.Television, numFrames int, duration float64) (fps float64, accuracy float64) {
func CalcFPS(tv *television.Television, numFrames int, duration float64) (fps float64, accuracy float64) {
fps = float64(numFrames) / duration
spec := tv.GetSpec()
accuracy = 100 * float64(numFrames) / (duration * float64(spec.FramesPerSecond))

View file

@ -30,7 +30,7 @@ import (
)
// Check is a very rough and ready calculation of the emulator's performance.
func Check(output io.Writer, profile bool, tv television.Television, runTime string, cartload cartridgeloader.Loader) error {
func Check(output io.Writer, profile bool, tv *television.Television, runTime string, cartload cartridgeloader.Loader) error {
var err error
// create vcs using the tv created above

View file

@ -50,7 +50,7 @@ type playmode struct {
// contents of the file specified in Filename field of the Loader instance will
// be checked. If it is a playback file then the playback codepath will be
// used.
func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload cartridgeloader.Loader, patchFile string, hiscoreServer bool, useSavekey bool) error {
func Play(tv *television.Television, scr gui.GUI, newRecording bool, cartload cartridgeloader.Loader, patchFile string, hiscoreServer bool, useSavekey bool) error {
var recording string
// if supplied cartridge name is actually a playback file then set

View file

@ -127,7 +127,7 @@ func (reg *LogRegression) regress(newRegression bool, output io.Writer, msg stri
output.Write([]byte(msg))
// create headless television. we'll use this to initialise the digester
tv, err := television.NewReference(reg.TVtype)
tv, err := television.NewTelevision(reg.TVtype)
if err != nil {
return false, "", curated.Errorf("log: %v", err)
}

View file

@ -109,7 +109,7 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
return false, "", curated.Errorf("playback: %v", err)
}
tv, err := television.NewReference(plb.TVSpec)
tv, err := television.NewTelevision(plb.TVSpec)
if err != nil {
return false, "", curated.Errorf("playback: %v", err)
}

View file

@ -180,7 +180,7 @@ func (reg *VideoRegression) regress(newRegression bool, output io.Writer, msg st
output.Write([]byte(msg))
// create headless television. we'll use this to initialise the digester
tv, err := television.NewReference(reg.TVtype)
tv, err := television.NewTelevision(reg.TVtype)
if err != nil {
return false, "", curated.Errorf("video: %v", err)
}