1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-01-21 10:12:20 +00:00

New hardware implementation:

- Orbit controller: implemented and passes ROS-less diagnostics
- ROS: In progress, not functional
- DAC: For Ted Kaehler's Smalltalk Music system (FM and Sampling).  Works, generates audio and can capture to WAV file.
- Organ keybard: Stub, enough implemented to make the music system happy (so it will play back music and not crash.)

Some minor cleanup.

New dependency on NAudio package for DAC playback.  Installer updated to include NAudio lib.
This commit is contained in:
Josh Dersch 2017-05-12 17:23:34 -07:00
parent bd31f629f7
commit 4bc85daa36
28 changed files with 2215 additions and 208 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
# Visual Studio 15
VisualStudioVersion = 15.0.26403.7
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contralto", "Contralto\Contralto.csproj", "{CC6D96B3-8099-4497-8AD8-B0795A3353EA}"
EndProject
@ -42,4 +42,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal

View File

@ -37,13 +37,15 @@ namespace Contralto
_scheduler = new Scheduler();
_memBus = new MemoryBus();
_mem = new Memory.Memory();
_mem = new Memory.Memory();
_keyboard = new Keyboard();
_diskController = new DiskController(this);
_displayController = new DisplayController(this);
_mouse = new Mouse();
_ethernetController = new EthernetController(this);
_musicInterface = new Music(this);
_orbitController = new OrbitController(this);
_audioDAC = new AudioDAC(this);
_organKeyboard = new OrganKeyboard(this);
_cpu = new AltoCPU(this);
@ -51,9 +53,10 @@ namespace Contralto
_memBus.AddDevice(_mem);
_memBus.AddDevice(_keyboard);
_memBus.AddDevice(_mouse);
_memBus.AddDevice(_musicInterface);
_memBus.AddDevice(_audioDAC);
_memBus.AddDevice(_organKeyboard);
Reset();
Reset();
}
public void Reset()
@ -70,9 +73,9 @@ namespace Contralto
_mouse.Reset();
_cpu.Reset();
_ethernetController.Reset();
_musicInterface.Reset();
_orbitController.Reset();
UCodeMemory.Reset();
UCodeMemory.Reset();
}
/// <summary>
@ -81,7 +84,7 @@ namespace Contralto
/// <param name="d"></param>
public void AttachDisplay(IAltoDisplay d)
{
_displayController.AttachDisplay(d);
_displayController.AttachDisplay(d);
}
public void DetachDisplay()
@ -96,11 +99,16 @@ namespace Contralto
{
_ethernetController.HostInterface.Shutdown();
}
//
// Allow the DAC to flush its output
//
_audioDAC.Shutdown();
}
public void SingleStep()
{
// Run every device that needs attention for a single clock cycle.
// Run every device that needs attention for a single clock cycle.
_memBus.Clock();
_cpu.Clock();
@ -224,6 +232,11 @@ namespace Contralto
get { return _ethernetController; }
}
public OrbitController OrbitController
{
get { return _orbitController; }
}
public Scheduler Scheduler
{
get { return _scheduler; }
@ -239,7 +252,9 @@ namespace Contralto
private DiskController _diskController;
private DisplayController _displayController;
private EthernetController _ethernetController;
private Music _musicInterface;
private OrbitController _orbitController;
private AudioDAC _audioDAC;
private OrganKeyboard _organKeyboard;
private Scheduler _scheduler;
}

View File

@ -17,16 +17,16 @@
<value>2</value>
</setting>
<setting name="Drive0Image" serializeAs="String">
<value/>
<value />
</setting>
<setting name="Drive1Image" serializeAs="String">
<value/>
<value />
</setting>
<setting name="HostAddress" serializeAs="String">
<value>34</value>
</setting>
<setting name="HostPacketInterfaceName" serializeAs="String">
<value/>
<value />
</setting>
<setting name="HostPacketInterfaceType" serializeAs="String">
<value>0</value>
@ -45,7 +45,19 @@
</setting>
<setting name="ThrottleSpeed" serializeAs="String">
<value>True</value>
</setting>
</setting>
<setting name="EnableWindowsFormsHighDpiAutoResizing" serializeAs="String">
<value>True</value>
</setting>
<setting name="EnableAudioDAC" serializeAs="String">
<value>True</value>
</setting>
<setting name="EnableAudioDACCapture" serializeAs="String">
<value>False</value>
</setting>
<setting name="AudioDACCapturePath" serializeAs="String">
<value />
</setting>
</Contralto.Properties.Settings>
</userSettings>
</configuration>

View File

@ -25,6 +25,7 @@ namespace Contralto.CPU
{
Invalid = -1,
Emulator = 0,
Orbit = 1,
DiskSector = 4,
Ethernet = 7,
MemoryRefresh = 8,
@ -52,6 +53,7 @@ namespace Contralto.CPU
_tasks[(int)TaskType.MemoryRefresh] = new MemoryRefreshTask(this);
_tasks[(int)TaskType.Ethernet] = new EthernetTask(this);
_tasks[(int)TaskType.Parity] = new ParityTask(this);
_tasks[(int)TaskType.Orbit] = new OrbitTask(this);
Reset();
}
@ -151,9 +153,9 @@ namespace Contralto.CPU
if (_currentTask != _nextTask)
{
_currentTask = _nextTask;
_currentTask.FirstInstructionAfterSwitch = true;
_currentTask.OnTaskSwitch();
}
_currentTask.FirstInstructionAfterSwitch = true;
_currentTask.OnTaskSwitch();
}
break;
case InstructionCompletion.MemoryWait:
@ -198,7 +200,7 @@ namespace Contralto.CPU
// Unsure if there is a deeper issue here or if there are other reset semantics
// in play that are not understood.
//
WakeupTask(CPU.TaskType.DiskSector);
WakeupTask(CPU.TaskType.DiskSector);
}
/// <summary>

View File

@ -194,6 +194,29 @@ namespace Contralto.CPU
EISFCT = 14,
}
/// <summary>
/// Orbit (print rasterizer) from OrbitGuide.press
/// </summary>
enum OrbitF1
{
OrbitBlock = 3,
OrbitDeltaWC = 12,
OrbitDBCWidthRead = 13,
OrbitOutputData = 14,
OrbitStatus = 15,
}
enum OrbitF2
{
OrbitDBCWidthSet = 8,
OrbitXY = 9,
OrbitHeight = 10,
OrbitFontData = 11,
OrbitInk = 12,
OrbitControl = 13,
OrbitROSCommand = 14,
}
/// <summary>
/// MicroInstruction encapsulates the decoding of a microinstruction word.
/// It also caches precomputed metadata related to the microinstruction that

View File

@ -16,7 +16,6 @@
*/
using Contralto.IO;
using Contralto.Logging;
using System;
namespace Contralto.CPU
@ -100,7 +99,7 @@ namespace Contralto.CPU
case DiskF1.LoadKSTAT:
// "KSTAT[12-15] are loaded from BUS[12-15]. (Actually BUS[13] is ORed onto
// KSTAT[13].)"
// KSTAT[13].)"
// From the schematic (and ucode source, based on the values it actually uses for BUS[13]), BUS[13]
// is also inverted. So there's that, too.
@ -109,7 +108,7 @@ namespace Contralto.CPU
int modifiedBusData = (_busData & 0xb) | ((~_busData) & 0x4);
// OR in BUS[12-15] after masking in KSTAT[13] so it is ORed in properly.
_diskController.KSTAT = (ushort)(((_diskController.KSTAT & 0xfff4)) | modifiedBusData);
_diskController.KSTAT = (ushort)(((_diskController.KSTAT & 0xfff4)) | modifiedBusData);
break;
case DiskF1.STROBE:
@ -127,9 +126,9 @@ namespace Contralto.CPU
switch (df2)
{
case DiskF2.INIT:
_nextModifier |= GetInitModifier();
break;
case DiskF2.INIT:
_nextModifier |= GetInitModifier();
break;
case DiskF2.RWC:
// "NEXT<-NEXT OR (IF current record to be written THEN 3 ELSE IF

View File

@ -88,7 +88,7 @@ namespace Contralto.CPU
{
case EmulatorF1.RSNF:
//
// Early:
// Early:
// "...decoded by the Ethernet interface, which gates the host address wired on the
// backplane onto BUS[8-15]. BUS[0-7] is not driven and will therefore be -1. If
// no Ethernet interface is present, BUS will be -1.
@ -122,7 +122,7 @@ namespace Contralto.CPU
break;
case EmulatorF1.STARTF:
// Dispatch function to Ethernet I/O based on contents of AC0.
// Dispatch function to Ethernet I/O based on contents of AC0.
if ((_busData & 0x8000) != 0)
{
//
@ -134,28 +134,35 @@ namespace Contralto.CPU
// field at the end of the cycle, setting this flag causes the main Task
// implementation to skip updating _mpc at the end of this instruction.
_softReset = true;
}
}
else if(_busData != 0)
{
//
// Dispatch to the appropriate device.
// The Ethernet controller is the only common device that is documented
// to have used STARTF, so we'll just go there directly; if other
// hardware is determined to be worth emulating we'll put together a more flexible dispatch.
//
if (_busData < 4)
switch(_busData)
{
_cpu._system.EthernetController.STARTF(_busData);
}
else
{
Log.Write(Logging.LogType.Warning, Logging.LogComponent.EmulatorTask, "STARTF for non-Ethernet device (code {0})",
Conversion.ToOctal(_busData));
case 1:
case 2:
case 3:
// Ethernet
_cpu._system.EthernetController.STARTF(_busData);
break;
case 4:
// Orbit
_cpu._system.OrbitController.STARTF(_busData);
break;
default:
Log.Write(Logging.LogType.Warning, Logging.LogComponent.EmulatorTask, "STARTF for non-Ethernet device (code {0})",
Conversion.ToOctal(_busData));
break;
}
}
break;
case EmulatorF1.SWMODE:
case EmulatorF1.SWMODE:
_swMode = true;
break;
@ -167,14 +174,14 @@ namespace Contralto.CPU
_wrtRam = true;
break;
case EmulatorF1.LoadESRB:
case EmulatorF1.LoadESRB:
_rb = (ushort)((_busData & 0xe) >> 1);
if (_rb != 0 && _systemType != SystemType.ThreeKRam)
{
// Force bank 0 for machines with only 1K RAM.
_rb = 0;
}
_rb = 0;
}
break;
default:

View File

@ -0,0 +1,173 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.Logging;
using System;
namespace Contralto.CPU
{
public partial class AltoCPU
{
/// <summary>
/// OrbitTask provides the implementation of the Orbit (printer rasterizer) controller
/// specific functions.
/// </summary>
private sealed class OrbitTask : Task
{
public OrbitTask(AltoCPU cpu) : base(cpu)
{
_taskType = TaskType.Orbit;
_wakeup = false;
}
public override void OnTaskSwitch()
{
// We put ourselves back to sleep immediately once we've started running.
//_wakeup = false;
}
protected override void ExecuteBlock()
{
//_wakeup = false;
_cpu._system.OrbitController.Stop();
}
protected override InstructionCompletion ExecuteInstruction(MicroInstruction instruction)
{
// TODO: get rid of polling.
//_wakeup = _cpu._system.OrbitController.Wakeup;
return base.ExecuteInstruction(instruction);
}
protected override ushort GetBusSource(MicroInstruction instruction)
{
//
// The Orbit task is wired to be a RAM-enabled task so it can use
// S registers.
// This code is stolen from the Emulator task; we should refactor this...
//
EmulatorBusSource ebs = (EmulatorBusSource)instruction.BS;
switch (ebs)
{
case EmulatorBusSource.ReadSLocation:
if (instruction.RSELECT != 0)
{
return _cpu._s[_rb][instruction.RSELECT];
}
else
{
// "...when reading data from the S registers onto the processor bus,
// the RSELECT value 0 causes the current value of the M register to
// appear on the bus..."
return _cpu._m;
}
case EmulatorBusSource.LoadSLocation:
// "When an S register is being loaded from M, the processor bus receives an
// undefined value rather than being set to zero."
_loadS = true;
return 0x0; // Technically this is an "undefined value," we're defining it as -1.
default:
throw new InvalidOperationException(String.Format("Unhandled bus source {0}", instruction.BS));
}
}
protected override void ExecuteSpecialFunction1Early(MicroInstruction instruction)
{
OrbitF1 of1 = (OrbitF1)instruction.F1;
switch (of1)
{
case OrbitF1.OrbitDeltaWC:
_busData &= _cpu._system.OrbitController.GetDeltaWC();
break;
case OrbitF1.OrbitDBCWidthRead:
_busData &= _cpu._system.OrbitController.GetDBCWidth();
break;
case OrbitF1.OrbitOutputData:
_busData &= _cpu._system.OrbitController.GetOutputDataAlto();
break;
case OrbitF1.OrbitStatus:
_busData &= _cpu._system.OrbitController.GetOrbitStatus();
// branch:
// "OrbitStatus sets NEXT[7] of IACS os *not* on, i.e. if Orbit is
// not in a character segment."
//
if (!_cpu._system.OrbitController.IACS)
{
_nextModifier |= 0x4;
}
break;
}
}
protected override void ExecuteSpecialFunction2(MicroInstruction instruction)
{
OrbitF2 of2 = (OrbitF2)instruction.F2;
switch (of2)
{
case OrbitF2.OrbitDBCWidthSet:
_cpu._system.OrbitController.SetDBCWidth(_busData);
break;
case OrbitF2.OrbitXY:
_cpu._system.OrbitController.SetXY(_busData);
break;
case OrbitF2.OrbitHeight:
_cpu._system.OrbitController.SetHeight(_busData);
// branch:
// "OrbitHeight sets NEXT[7] if the refresh timer has expired, i.e.
// if the image buffer needs refreshing."
//
if (_cpu._system.OrbitController.RefreshTimerExpired)
{
_nextModifier |= 0x4;
}
break;
case OrbitF2.OrbitFontData:
_cpu._system.OrbitController.WriteFontData(_busData);
break;
case OrbitF2.OrbitInk:
_cpu._system.OrbitController.WriteInkData(_busData);
break;
case OrbitF2.OrbitControl:
_cpu._system.OrbitController.Control(_busData);
break;
case OrbitF2.OrbitROSCommand:
_cpu._system.OrbitController.SendROSCommand(_busData);
break;
default:
throw new InvalidOperationException(String.Format("Unhandled orbit F2 {0}.", of2));
}
}
}
}
}

View File

@ -494,8 +494,9 @@ namespace Contralto.CPU
{
_cpu._l = aluData;
// Only RAM-related tasks can modify M. (Currently only the Emulator.)
if (_taskType == TaskType.Emulator)
// Only RAM-related tasks can modify M.
if (_taskType == TaskType.Emulator ||
_taskType == TaskType.Orbit)
{
_cpu._m = aluData;
}

View File

@ -185,6 +185,21 @@ namespace Contralto
/// </summary>
public static bool ThrottleSpeed;
/// <summary>
/// Whether to enable the DAC used for the Smalltalk music system.
/// </summary>
public static bool EnableAudioDAC;
/// <summary>
/// Whether to enable capture of the DAC output to file.
/// </summary>
public static bool EnableAudioDACCapture;
/// <summary>
/// The path to store DAC capture (if enabled).
/// </summary>
public static string AudioDACCapturePath;
public static string GetAltoIRomPath(string romFileName)
{
return Path.Combine("ROM", "AltoI", romFileName);
@ -198,7 +213,7 @@ namespace Contralto
public static string GetRomPath(string romFileName)
{
return Path.Combine("ROM", romFileName);
}
}
/// <summary>
/// Reads the current configuration file from the app's configuration.
@ -218,6 +233,9 @@ namespace Contralto
BootFile = (ushort)Properties.Settings.Default["BootFile"];
InterlaceDisplay = (bool)Properties.Settings.Default["InterlaceDisplay"];
ThrottleSpeed = (bool)Properties.Settings.Default["ThrottleSpeed"];
EnableAudioDAC = (bool)Properties.Settings.Default["EnableAudioDAC"];
EnableAudioDACCapture = (bool)Properties.Settings.Default["EnableAudioDACCapture"];
AudioDACCapturePath = (string)Properties.Settings.Default["AudioDACCapturePath"];
}
/// <summary>
@ -236,6 +254,9 @@ namespace Contralto
Properties.Settings.Default["BootFile"] = BootFile;
Properties.Settings.Default["InterlaceDisplay"] = InterlaceDisplay;
Properties.Settings.Default["ThrottleSpeed"] = ThrottleSpeed;
Properties.Settings.Default["EnableAudioDAC"] = EnableAudioDAC;
Properties.Settings.Default["EnableAudioDACCapture"] = EnableAudioDACCapture;
Properties.Settings.Default["AudioDACCapturePath"] = AudioDACCapturePath;
Properties.Settings.Default.Save();
}

View File

@ -94,6 +94,10 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="NAudio, Version=1.8.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.8.0\lib\net35\NAudio.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PcapDotNet.Base, Version=1.0.2.21699, Culture=neutral, PublicKeyToken=4b6f3e583145a652, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>pcap\PcapDotNet.Base.dll</HintPath>
@ -121,8 +125,12 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CPU\Tasks\OrbitTask.cs" />
<Compile Include="HighResTimer.cs" />
<Compile Include="IO\Music.cs" />
<Compile Include="IO\DoverROS.cs" />
<Compile Include="IO\AudioDAC.cs" />
<Compile Include="IO\OrbitController.cs" />
<Compile Include="IO\OrganKeyboard.cs" />
<Compile Include="IO\UDPEncapsulation.cs" />
<Compile Include="IO\IPacketEncapsulation.cs" />
<Compile Include="Properties\Resources.Designer.cs">
@ -271,6 +279,7 @@
<None Include="Disk\xmst76.dsk44">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>

View File

@ -40,7 +40,7 @@ namespace Contralto
public static string ToOctal(int i, int digits)
{
string octalString = Convert.ToString(i, 8);
string octalString = Convert.ToString(i, 8);
return new String('0', digits - octalString.Length) + octalString;
}
@ -49,6 +49,11 @@ namespace Contralto
/// </summary>
public static readonly ulong MsecToNsec = 1000000;
/// <summary>
/// Conversion from nanoseconds to milliseconds
/// </summary>
public static readonly double NsecToMsec = 0.000001;
/// <summary>
/// Conversion from microseconds to nanoseconds
/// </summary>

Binary file not shown.

231
Contralto/IO/AudioDAC.cs Normal file
View File

@ -0,0 +1,231 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.CPU;
using Contralto.Logging;
using Contralto.Memory;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace Contralto.IO
{
/// <summary>
/// Implements the hardware for the Audio DAC used by Ted Kaehler's
/// ST-74 Music System (either the FM-synthesis "TWANG" system or the
/// Sampling system.)
/// </summary>
public class AudioDAC : IMemoryMappedDevice
{
public AudioDAC(AltoSystem system)
{
_system = system;
_dacOutput = new Queue<ushort>(16384);
}
public void Shutdown()
{
if (_waveOut != null)
{
_waveOut.Stop();
_waveOut.Dispose();
}
if (_waveFile != null)
{
_waveFile.Close();
}
}
/// <summary>
/// Comments in the FM synthesis microcode indicate:
/// "240 SAMPLES = 18 msec"
/// Which works out to about 13.3Khz.
///
/// Unsure if this value also applies to the Sampling microcode, but
/// it sounds about right in action.
/// </summary>
public static readonly int AudioDACSamplingRate = 13000;
/// <summary>
/// Reads a word from the specified address.
/// </summary>
/// <param name="address"></param>
/// <param name="extendedMemory"></param>
/// <returns></returns>
public ushort Read(int address, TaskType task, bool extendedMemory)
{
// The DAC is, as far as I can tell, write-only.
return 0;
}
/// <summary>
/// Writes a word to the specified address.
/// </summary>
/// <param name="address"></param>
/// <param name="data"></param>
public void Load(int address, ushort data, TaskType task, bool extendedMemory)
{
if (Configuration.EnableAudioDAC)
{
// Ensure we have a sink for audio output capture if so configured.
if (Configuration.EnableAudioDACCapture && _waveFile == null)
{
string outputFile = Path.Combine(
Configuration.AudioDACCapturePath,
string.Format("AltoAudio-{0}.wav", DateTime.Now.ToString("yyyyMMdd-hhmmss")));
try
{
_waveFile = new WaveFileWriter(outputFile, new WaveFormat(AudioDAC.AudioDACSamplingRate, 1));
}
catch (Exception e)
{
Log.Write(LogType.Error,
LogComponent.DAC,
"Failed to create DAC output file {0}. Error: {1}", outputFile, e.Message);
}
}
// Ensure we have something to generate audio output with.
if (_waveOut == null)
{
_waveOut = new WaveOut();
_dacLock = new ReaderWriterLockSlim();
_waveOut.Init(new DACOutputWaveProvider(_dacOutput, _waveFile, _dacLock));
_waveOut.Play();
}
//
// Enter the Write lock to ensure consistency with the
// consumer (the DACOutputWaveProvider).
//
_dacLock.EnterWriteLock();
_dacOutput.Enqueue(data);
_dacLock.ExitWriteLock();
}
}
/// <summary>
/// Specifies the range (or ranges) of addresses decoded by this device.
/// </summary>
public MemoryRange[] Addresses
{
get { return _addresses; }
}
/// <summary>
/// From: http://bitsavers.org/pdf/xerox/alto/memos_1975/Reserved_Alto_Memory_Locations_Jan75.pdf
///
/// #177776: Digital-Analog Converter (DAC Hardware - Kaehler)
/// </summary>
private readonly MemoryRange[] _addresses =
{
new MemoryRange(0xfffe, 0xfffe),
};
private Queue<ushort> _dacOutput;
private ReaderWriterLockSlim _dacLock;
private AltoSystem _system;
private WaveOut _waveOut;
private WaveFileWriter _waveFile;
}
//
// Basic implementation of the NAudio WaveProvider32 class.
// on a Read, we fill the DAC with the samples the Alto has been generated.
// If it hasn't generated enough (it's fallen behind) we'll pad it to try
// and reduce popping/stuttering.
//
// It will also flush the output to a WaveFileWriter if it has been enabled in
// the configuration.
//
public class DACOutputWaveProvider : WaveProvider32
{
public DACOutputWaveProvider(Queue<ushort> dacOutput, WaveFileWriter waveFile, ReaderWriterLockSlim dacLock)
{
_dacOutput = dacOutput;
_waveFile = waveFile;
_dacLock = dacLock;
SetWaveFormat(AudioDAC.AudioDACSamplingRate, 1);
_lastSample = 0;
}
public override int Read(float[] buffer, int offset, int sampleCount)
{
int outSamples = 0;
_dacLock.EnterReadLock();
while(_dacOutput.Count > 0 && outSamples < sampleCount)
{
float sample = (float)_dacOutput.Dequeue() / 32768.0f - 1.0f;
buffer[offset + outSamples] = sample;
outSamples++;
_lastSample = sample;
}
_dacLock.ExitReadLock();
// Commit the Alto-generated samples to disk if we're saving them.
if (_waveFile != null)
{
_waveFile.WriteSamples(buffer, offset, outSamples);
}
//
// If we didn't have enough samples to fill the requested buffer,
// This means the Alto has fallen behind; pad the remaining buffer with the
// last written sample.
//
for (; outSamples < sampleCount; outSamples++)
{
buffer[offset + outSamples] = _lastSample;
}
return sampleCount;
}
/// <summary>
/// Queue containing the samples generated by the Alto.
/// We pull them off as WaveOut requests audio data.
/// </summary>
private Queue<ushort> _dacOutput;
/// <summary>
/// The last sample written. Used to pad the buffer if
/// the Alto falls behind.
/// </summary>
private float _lastSample;
/// <summary>
/// Used to ensure thread-safety of the _dacOutput queue
/// between the Alto emulation and the WaveOut thread.
/// </summary>
private ReaderWriterLockSlim _dacLock;
/// <summary>
/// Used to write the output to a WAV file, if the option
/// is enabled.
/// </summary>
private WaveFileWriter _waveFile;
}
}

685
Contralto/IO/DoverROS.cs Normal file
View File

@ -0,0 +1,685 @@
using Contralto.Logging;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Contralto.IO
{
public enum AdapterCommand
{
BufferReset = 0,
SetScales = 1,
SetBitClockRegister = 2,
SetMotorSpeedRegister = 3,
SetLineSyncDelayRegister = 4,
SetPageSyncDelayRegister = 5,
ExternalCommand1 = 6,
ExternalCommand2 = 7,
}
/// <summary>
/// Encapsulates the logic for both the ROS and the printer portions of the
/// print pipeline.
/// </summary>
public class DoverROS
{
public DoverROS(AltoSystem system)
{
_system = system;
_cs5Event = new Event(0, null, ColdStartCallback);
_innerLoopEvent = new Event(0, null, InnerLoopCallback);
_pageBuffer = new Bitmap(4096, 4096, PixelFormat.Format1bppIndexed);
Reset();
}
public void Reset()
{
_testMode = false;
_commandBeamOn = false;
_commandLocal = false;
_testPageSync = false;
_extendVideo = false;
_motorScale = 0;
_bitScale = 0;
_bitClock = 0;
_motorSpeed = 0;
_lineSyncDelay = 0;
_pageSyncDelay = 0;
_packetsOK = true;
_state = PrintState.ColdStart;
_innerLoopState = InnerLoopState.Idle;
_coldStartState = ColdStartState.SendVideoLow;
}
public void RecvCommand(ushort commandWord)
{
//
// Control of the adapter, ROS, and printer is accomplished with 16-bit commands.
// The high-order 4 bits of the command give a command code; the remaining 12 bits are
// used as an argument to the command.
//
_lastCommand = commandWord;
AdapterCommand command = (AdapterCommand)(commandWord >> 12);
int argument = commandWord & 0xfff;
switch (command)
{
case AdapterCommand.BufferReset:
break;
case AdapterCommand.SetScales:
_testMode = (argument & 0x1) != 0;
_commandBeamOn = (argument & 0x2) != 0;
_commandLocal = (argument & 0x4) != 0;
_testPageSync = (argument & 0x8) != 0;
_extendVideo = (argument & 0x20) != 0;
_motorScale = (argument & 0x1c0) >> 6;
_bitScale = (argument & 0xe00) >> 9;
break;
case AdapterCommand.SetBitClockRegister:
_bitClock = argument;
break;
case AdapterCommand.SetMotorSpeedRegister:
_motorSpeed = argument;
break;
case AdapterCommand.SetLineSyncDelayRegister:
_lineSyncDelay = argument;
break;
case AdapterCommand.SetPageSyncDelayRegister:
_pageSyncDelay = argument;
break;
case AdapterCommand.ExternalCommand1:
bool lastPrintRequest = (_externalCommand1 & 0x1) != 0;
_externalCommand1 = argument;
//
// Dover uses the low-order bit to provide the PrintRequest signal.
// A 0-to-1 transition of the bit tells the printer to start feeding
// sheets.
//
Log.Write(LogComponent.DoverROS, "ExternalCommand1 written {0}", argument);
if (lastPrintRequest && (_externalCommand1 & 0x1) == 0)
{
PrintRequest();
}
break;
case AdapterCommand.ExternalCommand2:
_videoGate = argument;
break;
default:
Log.Write(LogType.Error, LogComponent.DoverROS,
String.Format("Unhandled ROS command {0}", command));
break;
}
}
public int ReadStatus(int address)
{
//
// Address is a value from 0-63 (as specified by the Orbit's OrbitControl function)
// specifying the address of 4 status bits in the ROS's status memory.
// 4 status bits -- 4*address to 4*address+3 inclusive are returned.
//
int bitAddress = (address * 4);
ushort statusWord = ReadStatusWord(bitAddress >> 4);
int bitOffset = 12 - (bitAddress & 0xf);
return (statusWord & ((0xf) << bitOffset)) >> bitOffset;
}
private ushort ReadStatusWord(int wordNumber)
{
int value = 0;
switch (wordNumber)
{
case 0:
//
// Special status from the ROS:
// bit
// 0 - SendVideo
// 1 - PrintMode
// 2 - Local
// 3 - BeamEnable
// 4 - StatusBeamOn
//
value |= (_sendVideo ? 0x8000 : 0);
value |= (_printMode ? 0x4000 : 0);
value |= (_local ? 0x2000 : 0);
value |= (_beamEnable ? 0x1000 : 0);
value |= (_statusBeamOn ? 0x0800 : 0);
//Log.Write(LogComponent.DoverROS, "ROS word 0 bits 0-15: {0}", Conversion.ToOctal(value));
break;
case 1:
// A copy of the command most recently received by the adapter.
value = _lastCommand;
break;
case 2:
//
// Bit clock:
// bit
// 0 - VideoPolarity
// 1-3 - BitScale
// 4-15 - BitClock
//
value |= (_videoPolarity ? 0x8000 : 0);
value |= (_bitScale << 12);
value |= _bitClock;
break;
case 3:
//
// Motor speed
// bit
// 0 - SelectLeadEdge
// 1-3 - MotorScale
// 4-15 - MotorSpeed
value |= (_selectLeadEdge ? 0x8000 : 0);
value |= (_motorScale << 12);
value |= _motorSpeed;
break;
case 4:
//
// Line sync delay
// bit
// 0 - Switch3
// 2 - ExtendVideo
// 3 - TestPageSync
// 4-15 - LineSyncDelay
//
value |= (_switch3 ? 0x8000 : 0);
value |= (_extendVideo ? 0x2000 : 0);
value |= (_testPageSync ? 0x1000 : 0);
value |= _lineSyncDelay;
break;
case 5:
//
// Page sync delay
// bit
// 0 - Switch4
// 1 - CommandLocal
// 2 - CommandBeamOn
// 3 - TestMode
// 4-15 - PageSyncDelay
//
value |= (_switch4 ? 0x8000 : 0);
value |= (_commandLocal ? 0x4000 : 0);
value |= (_commandBeamOn ? 0x2000 : 0);
value |= (_testMode ? 0x1000 : 0);
value |= _pageSyncDelay;
break;
case 6:
//
// External Command 1
// bit
// 0 - LineNoise
// 1 - CompareError
// 2 - BufferUnderflow
// 3 - PacketsOK
// 4-12 - ExternalCommand1
//
value |= (_lineNoise ? 0x8000 : 0);
value |= (_compareError ? 0x4000 : 0);
value |= (_bufferUnderflow ? 0x2000 : 0);
value |= (_packetsOK ? 0x1000 : 0);
value |= _externalCommand1;
break;
case 7:
//
// bit
// 0-3 - LineCount
// 4-15 - VideoGate
//
value |= (_lineCount << 12);
value |= _videoGate;
break;
case 8:
// Special Dover status bits 0-15
// Count-H -- indicates that a page is ready to be printed.
value |= (_countH ? 0x1000 : 0);
//
// OR in status bits that are expected to be "1"
// for normal operation (i.e. no malfunctions).
// These are:
// 8 - LS4 (adequate paper in tray)
// 11 - LaserOn
// 13 - ReadyTemp
value |= 0x214;
Log.Write(LogComponent.DoverROS, "Dover bits 0-15: {0}", Conversion.ToOctal(value));
break;
case 9:
// Special Dover status bits 16-31
//
// OR in status bits that are expected to be "1"
// for normal operation (i.e. no malfunctions).
// These are:
// 5 - ACMonitor
// 13 - LS24 & LS31
value |= 0x2004;
Log.Write(LogComponent.DoverROS, "Dover bits 16-31: {0}", Conversion.ToOctal(value));
break;
case 10:
value = _id;
Log.Write(LogComponent.DoverROS, "Device ID {0}", Conversion.ToOctal(value));
break;
case 11:
value = _serialNumber;
Log.Write(LogComponent.DoverROS, "Device Serial: {0}", Conversion.ToOctal(value));
break;
default:
Log.Write(LogComponent.DoverROS, "Unhandled ROS status word {0}", wordNumber);
break;
}
return (ushort)value;
}
private void PrintRequest()
{
switch(_state)
{
case PrintState.ColdStart:
if (!_printMode)
{
_printMode = true;
_sendVideo = false;
// Queue a 250ms event to fire CS-5(0).
// and 990ms item to cancel printing if a second
// print-request isn't received.
_innerLoopState = InnerLoopState.CS5;
_innerLoopEvent.TimestampNsec = (ulong)(120 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
Log.Write(LogComponent.DoverROS, "Cold Start initialized at {0}ms -- CS-5(0) in 250ms.", _system.Scheduler.CurrentTimeNsec * Conversion.NsecToMsec);
}
else
{
Log.Write(LogComponent.DoverROS, "PrintRequest received in cold start at {0}ms.", _system.Scheduler.CurrentTimeNsec * Conversion.NsecToMsec);
_keepGoing = true;
}
break;
case PrintState.InnerLoop:
if (!_printMode)
{
// PrintRequest too late.
Log.Write(LogComponent.DoverROS, "PrintRequest too late. Ignoring.");
}
else
{
if (_innerLoopState != InnerLoopState.Idle)
{
Log.Write(LogComponent.DoverROS, "PrintRequest received in inner loop.");
_keepGoing = true;
}
else
{
//
// Currently idle: Kick off the first round of the inner loop.
// Queue a PageSyncDelay (250ms) event to pulse SendVideo
// after the pulse, gather video raster from Orbit
//
Log.Write(LogComponent.DoverROS, "PrintRequest received, starting inner loop.");
_innerLoopState = InnerLoopState.CS5;
_innerLoopEvent.TimestampNsec = 120 * Conversion.MsecToNsec;
_system.Scheduler.Schedule(_innerLoopEvent);
}
}
break;
case PrintState.Runout:
Log.Write(LogComponent.DoverROS, "Runout.");
break;
}
}
private void InnerLoopCallback(ulong timestampNsec, ulong delta, object context)
{
switch(_innerLoopState)
{
case InnerLoopState.CS5:
_countH = false;
_sendVideo = false;
// Keep SendVideo low for 125ms
_innerLoopState = InnerLoopState.SendVideo;
_innerLoopEvent.TimestampNsec = 125 * Conversion.MsecToNsec;
_system.Scheduler.Schedule(_innerLoopEvent);
Log.Write(LogComponent.DoverROS, "Inner loop: CS5");
break;
case InnerLoopState.SendVideo:
_sendVideo = true;
_innerLoopState = InnerLoopState.ReadBands;
_readBands = 0;
// time for one band of 16 scanlines to be read (approx.)
_innerLoopEvent.TimestampNsec = (ulong)(0.2 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
Log.Write(LogComponent.DoverROS, "Inner loop: SendVideo");
break;
case InnerLoopState.ReadBands:
// Assume 3000 scanlines for an 8.5" sheet of paper at 350dpi.
// If the Orbit is allowing us to read the output buffer then
// we will do so, otherwise we emit nothing.
if (_readBands > 3000)
{
if (_state == PrintState.ColdStart)
{
_innerLoopState = InnerLoopState.ColdStartEnd;
}
else
{
_innerLoopState = InnerLoopState.CountH;
}
}
else
{
if (_system.OrbitController.SLOTTAKE)
{
// Read in 16 scanlines of data -- this is 256x16 words
for (int y = 0; y < 16; y++)
{
for (int x = 0; x < 256 - _system.OrbitController.FA; x++)
{
ushort word = _system.OrbitController.GetOutputDataROS();
_pageData[(_readBands + y) * 512 + x * 2] = (byte)(word & 0xff);
_pageData[(_readBands + y) * 512 + x * 2 + 1] = (byte)(word >> 8);
}
}
Log.Write(LogComponent.DoverROS, "Read bands {0}", _readBands);
}
else
{
// Nothing right now
Log.Write(LogComponent.DoverROS, "No bands available from Orbit.");
}
_readBands += 16;
if (_readBands > 2500 && !_countH)
{
_countH = true;
Log.Write(LogComponent.DoverROS, "Enabling CountH");
}
}
// time for one band of 16 scanlines to be read (approx.)
_innerLoopEvent.TimestampNsec = (ulong)(0.2 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
break;
case InnerLoopState.CountH:
_countH = false;
if (_keepGoing)
{
// PrintRequest during ColdStart -- move to Inner loop
_keepGoing = false;
_state = PrintState.InnerLoop;
_innerLoopState = InnerLoopState.CS5;
Log.Write(LogComponent.DoverROS, "PrintRequest during ColdStart -- moving to inner loop.");
}
else
{
_innerLoopState = InnerLoopState.Idle;
Log.Write(LogComponent.DoverROS, "No PrintRequest during ColdStart -- idling.");
}
break;
case InnerLoopState.ColdStartEnd:
if (_keepGoing)
{
//
// Got a PrintRequest during cold start, start the inner loop.
//
_keepGoing = false;
_state = PrintState.InnerLoop;
_innerLoopState = InnerLoopState.CS5;
Log.Write(LogComponent.DoverROS, "Inner loop: CountH -- continuing");
}
else
{
//
// No Print Request; idle and shut down.
//
_innerLoopState = InnerLoopState.Idle;
Log.Write(LogComponent.DoverROS, "Inner loop: CountH -- idling");
}
// Debug: Put picture in image
/*
BitmapData data = _pageBuffer.LockBits(_pageRect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
IntPtr ptr = data.Scan0;
System.Runtime.InteropServices.Marshal.Copy(_pageData, 0, ptr, _pageData.Length);
_pageBuffer.UnlockBits(data);
EncoderParameters p = new EncoderParameters(1);
p.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
_pageBuffer.Save(String.Format("c:\\temp\\raster{0}.png", _rasterNum++), GetEncoderForFormat(ImageFormat.Png), p);
*/
_innerLoopEvent.TimestampNsec = (ulong)(150 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
_innerLoopEvent.TimestampNsec = (ulong)(150 * Conversion.MsecToNsec);
_system.Scheduler.Schedule(_innerLoopEvent);
break;
case InnerLoopState.Idle:
_countH = false;
_sendVideo = false;
_printMode = false;
_state = PrintState.ColdStart;
Log.Write(LogComponent.DoverROS, "Inner loop: Idle");
break;
}
}
private void ColdStartCallback(ulong timestampNsec, ulong delta, object context)
{
switch (_coldStartState)
{
case ColdStartState.SendVideoLow:
_sendVideo = false;
// Keep SendVideo low for 125ms
_coldStartState = ColdStartState.SendVideoHigh;
_cs5Event.TimestampNsec = 125 * Conversion.MsecToNsec;
_system.Scheduler.Schedule(_cs5Event);
Log.Write(LogComponent.DoverROS, "Cold start: toggle SendVideo low");
break;
case ColdStartState.SendVideoHigh:
_sendVideo = true;
_coldStartState = ColdStartState.Timeout;
_cs5Event.TimestampNsec = 800 * Conversion.MsecToNsec;
_system.Scheduler.Schedule(_cs5Event);
Log.Write(LogComponent.DoverROS, "Cold start: toggle SendVideo high");
break;
case ColdStartState.Timeout:
if (_state == PrintState.ColdStart)
{
// Never moved from ColdStart, Alto never sent another PageRequest.
Log.Write(LogComponent.DoverROS, "Cold start timeout. Aborting.");
_sendVideo = false;
_printMode = false;
}
break;
}
}
private ImageCodecInfo GetEncoderForFormat(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
private enum PrintState
{
ColdStart = 0,
InnerLoop,
Runout
}
private enum ColdStartState
{
SendVideoLow,
SendVideoHigh,
Timeout
}
private enum InnerLoopState
{
Idle = 0,
CS5,
SendVideo,
ReadBands,
CountH,
ColdStartEnd,
}
private PrintState _state;
private InnerLoopState _innerLoopState;
private ColdStartState _coldStartState;
private bool _keepGoing;
private int _readBands;
private AltoSystem _system;
// Last command sent to us
private ushort _lastCommand;
// Command registers
private bool _testMode;
private bool _commandBeamOn;
private bool _commandLocal;
private bool _testPageSync;
private bool _extendVideo;
private int _motorScale;
private int _bitScale;
private int _bitClock;
private int _motorSpeed;
private int _lineSyncDelay;
private int _pageSyncDelay;
private int _externalCommand1;
private int _videoGate;
// Status registers
private bool _sendVideo;
private bool _printMode;
private bool _local;
private bool _beamEnable = true;
private bool _statusBeamOn;
private bool _lineNoise;
private bool _compareError;
private bool _bufferUnderflow;
private bool _packetsOK = true;
private int _lineCount;
// Physical switches (test and otherwise)
private bool _videoPolarity;
private bool _selectLeadEdge;
private bool _switch3;
private bool _switch4;
// ID and serial number
private ushort _id;
private ushort _serialNumber;
//
// Dover specific status that we care to report.
// TODO: if we're printing to a real printer, we could choose
// to raise status (malfunction bits, etc.) that correspond to
// real printer status...
//
//
// This signal is raised if a sheet has been successfully fed to
// receive the image for the page being printed.
// Count-H comes on 896ms after CS-5 and persists until the next
// CS-5. (Note -- CS-5 is transmitted as the PageSync signal)
//
private bool _countH;
// Events to drive the print state machine
//
private Event _cs5Event;
private Event _innerLoopEvent;
private byte[] _pageData = new byte[4096 * 512];
private Rectangle _pageRect = new Rectangle(0, 0, 4096, 4096);
private Bitmap _pageBuffer;
private int _rasterNum;
}
}

View File

@ -145,13 +145,13 @@ namespace Contralto.IO
}
AltoKeyBit bits = _keyMap[key];
_keyWords[bits.Word] |= _keyMap[key].Bitmask;
_keyWords[bits.Word] |= bits.Bitmask;
}
public void KeyUp(AltoKey key)
{
AltoKeyBit bits = _keyMap[key];
_keyWords[bits.Word] &= (ushort)~_keyMap[key].Bitmask;
_keyWords[bits.Word] &= (ushort)~bits.Bitmask;
}
public void PressBootKeys(ushort bootAddress, bool netBoot)

View File

@ -1,133 +0,0 @@
/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.CPU;
using Contralto.Logging;
using Contralto.Memory;
using System.IO;
namespace Contralto.IO
{
/// <summary>
/// Implements the hardware for Ted Kaehler's organ keyboard and DAC
/// </summary>
public class Music : IMemoryMappedDevice
{
public Music(AltoSystem system)
{
//_musicIo = new FileStream("c:\\alto\\mus.snd", FileMode.Create, FileAccess.ReadWrite);
_system = system;
Reset();
}
~Music()
{
//_musicIo.Close();
}
public void Reset()
{
_foo = true;
}
/// <summary>
/// Reads a word from the specified address.
/// </summary>
/// <param name="address"></param>
/// <param name="extendedMemory"></param>
/// <returns></returns>
public ushort Read(int address, TaskType task, bool extendedMemory)
{
// debug for kaehler's music st
Log.Write(LogType.Verbose, LogComponent.Music, "MUSIC (I/O) read from {0} by task {1} (bank {2}), Nova PC {3}",
Conversion.ToOctal(address),
task,
UCodeMemory.GetBank(task),
Conversion.ToOctal(_system.CPU.R[6]));
if (address == 0xfffe)
{
return _lastDac;
}
else
{
_foo = !_foo;
if (!_foo)
{
return 0x800;
}
else
{
return 0;
}
}
}
/// <summary>
/// Writes a word to the specified address.
/// </summary>
/// <param name="address"></param>
/// <param name="data"></param>
public void Load(int address, ushort data, TaskType task, bool extendedMemory)
{
Log.Write(LogType.Verbose, LogComponent.Music, "MUSIC (I/O) write to {0} ({1}) by task {2} (bank {3})",
Conversion.ToOctal(address),
Conversion.ToOctal(data),
task,
UCodeMemory.GetBank(task));
if (address == 0xfffe)
{
//_musicIo.WriteByte((byte)(data >> 8));
//_musicIo.WriteByte((byte)data);
_lastDac = data;
}
}
/// <summary>
/// Specifies the range (or ranges) of addresses decoded by this device.
/// </summary>
public MemoryRange[] Addresses
{
get { return _addresses; }
}
/// <summary>
/// From: http://bitsavers.org/pdf/xerox/alto/memos_1975/Reserved_Alto_Memory_Locations_Jan75.pdf
///
/// #177140 - #177157: Organ Keyboard (Organ Hardware - Kaehler)
/// #177776: Digital-Analog Converter (DAC Hardware - Kaehler)
/// </summary>
private readonly MemoryRange[] _addresses =
{
new MemoryRange(0xfe60, 0xfe6f), // Organ
new MemoryRange(0xfffe, 0xfffe) // DAC
};
private ushort _lastDac;
private AltoSystem _system;
private FileStream _musicIo;
private bool _foo;
}
}

View File

@ -0,0 +1,639 @@
using Contralto.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Contralto.IO
{
/// <summary>
/// Implements the Orbit controller -- hardware for generating
/// rasters to be sent to a ROS (Raster Output Scanner) device
/// such as a laser printer.
/// </summary>
public class OrbitController
{
public OrbitController(AltoSystem system)
{
_system = system;
_ros = new DoverROS(system);
_refreshEvent = new Event(_refreshInterval, null, RefreshCallback);
Reset();
}
public bool RefreshTimerExpired
{
get { return _refresh; }
}
public bool IACS
{
get { return _iacs; }
}
public bool SLOTTAKE
{
get { return _slottake; }
}
public int FA
{
get { return _fa; }
}
public void Reset()
{
//
// A Reset performs at least the following functions:
// FA <- 0, SLOTTAKE <- 0, band buffer A is assigned
// to the image buffer, the status and control dialogs
// with the adapter are reset.
//
_fa = 0;
_slottake = false;
_image = _a;
_output = _b;
_incon = false;
_outputY = 0;
_outputX = 0;
_run = false;
_iacs = false;
_refresh = false;
_goAway = false;
_behind = false;
_stableROS = true;
_badROS = false;
Log.Write(LogComponent.Orbit, "Orbit reset.");
}
public bool Wakeup
{
get
{
return _run &&
((_refresh || !_goAway) &&
(true || !_iacs)); // tautology -- the 'true' is "FIFO ready" and since I don't implement the FIFO (yet...)
}
}
public void STARTF(ushort value)
{
// Kick off the refresh timer if it's not already pending
// Wake up task, etc.
_run = true;
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
// "IACS is cleared by StartIO(4)"
_iacs = false;
if (!_refreshRunning)
{
_refreshRunning = true;
_refreshEvent.TimestampNsec = _refreshInterval;
_system.Scheduler.Schedule(_refreshEvent);
}
Log.Write(LogComponent.Orbit, "Orbit started.");
}
public void Stop()
{
_run = false;
_system.CPU.BlockTask(CPU.TaskType.Orbit);
//Log.Write(LogComponent.Orbit, "Orbit blocked.");
}
public void Control(ushort value)
{
//
// This function transfers 16 bits of control information to Orbit.
//
//
// This 8-bit field has two different interpretations, depending on
// the setting of the WHICH field.
//
int auxControl = (value & 0xff00) >> 8;
//
// This bit, if 1, will reset Orbit entirely.
//
if ((value & 0x1) != 0)
{
Reset();
}
//
// This bit controls refresh logic, and should normally be 0.
//
if ((value & 0x2) != 0)
{
_refresh = false;
}
//
// This bit controls the use to which the 8-bit auxControl field is
// put. If WHICH=0, auxControl is interpreted as an address (range 0
// to 63) into the adapter status memory: when OrbitStatus is next
// interrogated, 4 status bits (with numbers 4*auxControl to
// 4 *auxControl + 3 will be reported. If WHICH=1, auxControl is used
// to set FA.
//
if ((value & 0x4) != 0)
{
_fa = auxControl;
_outputY = _fa;
// "The setting of FA provided by the Alto is examined only when
// Orbit reads out the last 16 bits of a scanline..."
// (So we *should* leave _outputY alone here).
}
else
{
_statusAddress = auxControl & 0x3f;
}
//
// This bit controls microcode wakeups, and should normally be 0.
//
if ((value & 0x8) != 0)
{
_goAway = true;
_system.CPU.BlockTask(CPU.TaskType.Orbit);
}
//
// This bit clears the BEHIND indicator.
//
if ((value & 0x10) != 0)
{
_behind = false;
}
//
// ESS - This bit must be 1 to enable changing the SLOTTAKE setting.
// SLOTTAKE - This bit setting, enabled by ESS above, controls the
// output buffer logic. Normally (SLOTTAKE=0), Orbit will not honor
// video data requests coming from the adapter. As soon as SLOTTAKE
// is set to 1, however, output data will be passed to the adapter when
// it demands it.
//
if ((value & 0xc0) == 0xc0)
{
_slottake = true;
}
Log.Write(LogComponent.Orbit,
"Set Control: aux {0}, reset {1} refresh {2} which {3} goaway {4} behind {5} slottake {6}",
auxControl, (value & 0x1) != 0, (value & 0x2) != 0, (value & 0x4) != 0, _goAway, _behind, _slottake);
}
public void SetHeight(ushort value)
{
//
// This command sends to Orbit a 12-bit field (value[4-15]) which is
// interpreted as the two's complement of the height of the source raster,
// in bits.
//
_height = 4096 - (value & 0xfff);
Log.Write(LogComponent.Orbit,
"Set Height: {0}", _height);
}
public void SetXY(ushort value)
{
//
// This commands sets x <- value[0-3] and y <- value[4-15].
// It is therefore used to set the starting scan-line within
// the band (x) and the vertical position of the bottom of the
// copy of the source raster (y).
_y = value & 0x0fff;
_x = (value & 0xf000) >> 12;
Log.Write(LogComponent.Orbit,
"Set XY: X={0}, Y={1}", _x, _y);
}
public void SetDBCWidth(ushort value)
{
//
// value[0-3] tells Orbit which bit (0-15) of the first
// word of raster data is the first bit to be examined.
//
_deltaBC = _deltaBCEnd = (value & 0xf000) >> 12;
//
// value[4-15] is interpreted as the width of the source
// raster, minus 1.
///
_width = (value & 0x0fff);
//
// Third, executing this function initializes a mess of logic
// relating to transferring a source raster to Orbit and sets
// IACS ("in a character segment"). After the function is
// executed it is wise to issue only OrbitFontData functions until
// the image-generation for this character terminates (i.e. IACS
// becomes 0). IACS is cleared by StartIO(4).
// Start a new character
//
_iacs = true;
_firstWord = true;
_bitCount = 0;
_cx = _x;
_cy = _y;
_scanlines = (_x + (_width + 1) < 16) ? (_width + 1): 16 - _x;
Log.Write(LogComponent.Orbit,
"Set DBCWidth: DeltaBC {0}, Width {1}", _deltaBC, _width);
}
public void WriteFontData(ushort value)
{
//
// This function is used to send 16-bit raster data words to Orbit,
// usually in a very tight loop. The loop is exited when IACS becomes
// 0, that is, when the Orbit hardware counts the width counter down to
// zero, or when the x counter overflows (i.e. when it would count up
// from 15 to 16).
//
if (!_iacs)
{
Log.Write(LogType.Error,
LogComponent.Orbit, "Unxpected OrbitFontData while IACS false.");
return;
}
//
// We insert the word one bit at a time; this is more costly
// computationally but it's a ton simpler than dealing with word
// and scanline boundaries and merging entire words at a time.
//
int startBit = 15;
if (_firstWord)
{
startBit = 15 - _deltaBC;
}
for(int i = startBit; i >=0 ;i--)
{
int bitValue = (value & (0x1 << i)) == 0 ? 0 : 1;
if (bitValue != 0 && _cy < 4096) // clip to end of band
{
SetImageRasterBit(_cx, _cy, bitValue);
}
_cy++;
_bitCount++;
_deltaBCEnd++;
if (_cy > _y + _height - 1)
{
_cy = _y;
_cx++;
_width--;
if (_cx > 15 || _width < 0)
{
_iacs = false;
Log.Write(LogComponent.DoverROS, "Image band completed.");
break;
}
}
}
_firstWord = false;
Log.Write(LogComponent.Orbit,
"Font Data: {0}", Conversion.ToOctal(value));
}
public void WriteInkData(ushort value)
{
//
// This function sets 16 bits of INK memory: INK[x,0] through INK[x,15],
// where x has been previously set with OrbitXY.
//
_ink[_x] = value;
Log.Write(LogComponent.Orbit,
"Ink Data[{0}]: {1}", _x, Conversion.ToOctal(value));
}
public void SendROSCommand(ushort value)
{
//
// This function sends a 16-bit command to the ROS.
// Another ROS command should not be issued until 60 microseconds
// have elapsed, to allow time for proper transmission of the command
// to the ROS.
//
_ros.RecvCommand(value);
Log.Write(LogComponent.Orbit,
"ROS command {0}", Conversion.ToOctal(value));
}
public ushort GetOutputDataAlto()
{
if (_slottake)
{
Log.Write(LogType.Error, LogComponent.Orbit,
"Unexpected OrbitOutputData to Alto with SLOTTAKE set.");
}
return GetOutputData();
}
public ushort GetOutputDataROS()
{
if (!_slottake)
{
Log.Write(LogType.Error, LogComponent.Orbit,
"Unexpected OrbitOutputData to ROS without SLOTTAKE set.");
}
return GetOutputData();
}
public ushort GetDeltaWC()
{
Log.Write(LogComponent.Orbit,
"Delta WC {0} ({1} bits)", (_bitCount >> 4), _bitCount);
return (ushort)(_bitCount >> 4);
}
public ushort GetDBCWidth()
{
Log.Write(LogComponent.Orbit,
"Delta DBCWidth {0},{1}", _deltaBCEnd % 16, _width);
return (ushort)((_deltaBCEnd << 12) | (_width & 0xfff));
}
public ushort GetOrbitStatus()
{
int result = _ros.ReadStatus(_statusAddress);
if (_behind)
{
result |= 0x10;
}
if (_stableROS)
{
result |= 0x20;
}
if (_incon)
{
result |= 0x40;
}
if (_badROS)
{
result |= 0x80;
}
Log.Write(LogComponent.Orbit,
"OrbitStatus {0}", Conversion.ToOctal(result));
return (ushort)result;
}
private ushort GetOutputData()
{
//
// Basically, the function OrbitOutputData reads 16 bits from the output buffer
// into the Alto.
//
// The programmer (or emulator author) needs to be aware of two tricky details
// concerning readout:
// 1. There is a 16-bit buffer (ROB) that sits between the addressing mechanism
// and the outside world (the taker of data: the Alto or the ROS adapter). As
// a result, immediately after the buffers "switch" the ROB contains the last
// 16-bits of data from the previous buffer (x=15, y=4080 through y=4095).
// The usual convention is to read out this one last word. This leaves the first
// word of the new buffer in ROB.
//
// 2. The output band buffer will not be refreshed ... (not a concern here)
//
// Return the last value from ROB
ushort value = _rob;
// Update ROB
_rob = _output[_outputX, _outputY];
// Clear read word
_output[_outputX, _outputY] = 0;
//
// Move to next word
//
_outputY++;
if (_outputY == 256)
{
Log.Write(LogComponent.DoverROS,
"OutputData - scanline {0} completed", _outputX);
_outputY = _fa;
_outputX++;
if (_outputX == 16)
{
// Done reading output band -- swap buffers!
Log.Write(LogComponent.DoverROS,
"OutputData - buffer read completed.");
SwapBuffers();
_outputX = 0;
}
}
Log.Write(LogComponent.Orbit,
"OutputData {0}", Conversion.ToOctal(value));
return value;
}
private void SwapBuffers()
{
ushort[,] _temp = _image;
_image = _output;
_output = _temp;
_outputY = _fa;
_outputX = 0;
//
// If input is still being written,
// the Alto is now behind the consumer...
//
if (!_goAway)
{
Console.WriteLine("BEHIND");
_behind = true;
}
else
{
// "The buffer switch will turn off GOAWAY and consequently
// allow wakeups again."
_goAway = false;
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
}
// Flip buffer bit
_incon = !_incon;
}
private void SetImageRasterBit(int x, int y, int bitValue)
{
// Pick out the word we're interested in
int wordAddress = y >> 4;
ushort inputWord = _image[x, wordAddress];
// grab the ink bit and OR it in.
int inkBit = (_ink[x] & (0x8000 >> (y % 16)));
_image[x, wordAddress] = (ushort)(inputWord | inkBit);
}
private void RefreshCallback(ulong timeNsec, ulong skewNsec, object context)
{
_refresh = true;
//Log.Write(LogComponent.Orbit, "Refresh signal raised.");
if (_run)
{
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
// do it again
_refreshEvent.TimestampNsec = _refreshInterval;
_system.Scheduler.Schedule(_refreshEvent);
}
else
{
//Log.Write(LogComponent.Orbit, "RUN deasserted, Refresh aborted.");
_refreshRunning = false;
}
}
// Run status
private bool _run;
// Raster information
private int _height; // height (in bits)
private int _x; // scanline
private int _y; // bit position in scanline
private int _width; // raster width of character
private int _bitCount; // count of processed bits; used to calculate DeltaWC.
private int _deltaBC; // bit of first word to start with
private int _deltaBCEnd; // number of bits left in last word rasterized
private int _scanlines;
// FA -- "First Address" of each scanline -- a value between
// 0 and 255 indicating the starting word to be read by the
// output device (either the Alto or the ROS).
private int _fa;
// SLOTTAKE - indicates that the ROS takes data (not the Alto)
private bool _slottake;
// Address to pull ROS status bits from
private int _statusAddress;
// Signals completion of input from Alto
private bool _goAway;
// Whether the Alto has fallen behind the ROS's consumption
private bool _behind;
// Current raster position
private bool _firstWord;
private int _cx;
private int _cy;
//
// IACS ("in a character segment") is set by OrbitDBCWidthSet
// and remains set until:
// - StartIO(4) is invoked
// - The Width counter counts down to zero
// - The current band is completed (the x counter overflows to 16)
//
private bool _iacs;
//
// Refresh timer expiry
//
private bool _refresh;
//
// Output band position and data
//
private int _outputX;
private int _outputY; // in words, starting at FA
private ushort _rob; // Read Output Buffer
//
// Buffer status -- if 0, A is the image buffer, B is the
// output buffer. If 1, these are swapped.
//
private bool _incon;
//
// ROS status
//
private bool _badROS;
private bool _stableROS;
// Raster bands
private ushort[,] _a = new ushort[16, 256]; // 16x4096 bits
private ushort[,] _b = new ushort[16, 256];
// Ink data
private ushort[] _ink = new ushort[16];
// buffer references that are swapped
private ushort[,] _image;
private ushort[,] _output;
//
// Refresh event and interval
private bool _refreshRunning;
private Event _refreshEvent;
private readonly ulong _refreshInterval = 2 * Conversion.MsecToNsec; // 2ms in nsec
//
// The ROS we talk to to get raster onto a "page"
//
private DoverROS _ros;
private AltoSystem _system;
}
}

View File

@ -0,0 +1,101 @@
using Contralto.CPU;
using Contralto.Logging;
using Contralto.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Contralto.IO
{
/// <summary>
/// Implements the Organ Keyboard interface used by the ST-74
/// Music System. Very little is known about the hardware at this time,
/// so most of this is speculation or based on disassembly/reverse-engineering
/// of the music system code.
///
/// This is currently a stub that implements the bare minimum to make the
/// music system think there's a keyboard attached to the system.
/// </summary>
public class OrganKeyboard : IMemoryMappedDevice
{
public OrganKeyboard(AltoSystem system)
{
_system = system;
Reset();
}
public void Reset()
{
//
// Initialize keyboard registers.
// Based on disassembly of the Nova code that drives the keyboard
// interface, the top 6 bits are active low.
//
for (int i = 0; i < 16; i++)
{
_keyData[i] = (ushort)(0xfc00);
}
}
/// <summary>
/// Reads a word from the specified address.
/// </summary>
/// <param name="address"></param>
/// <param name="extendedMemory"></param>
/// <returns></returns>
public ushort Read(int address, TaskType task, bool extendedMemory)
{
Log.Write(LogType.Verbose, LogComponent.Organ, "Organ read from {0} by task {1} (bank {2}), Nova PC {3}",
Conversion.ToOctal(address),
task,
UCodeMemory.GetBank(task),
Conversion.ToOctal(_system.CPU.R[6]));
return _keyData[address - 0xfe60];
}
/// <summary>
/// Writes a word to the specified address.
/// </summary>
/// <param name="address"></param>
/// <param name="data"></param>
public void Load(int address, ushort data, TaskType task, bool extendedMemory)
{
// The registers are write-only as far as I've been able to ascertain.
Log.Write(LogType.Verbose, LogComponent.Organ, "Unexpected organ write to {0} ({1}) by task {2} (bank {3})",
Conversion.ToOctal(address),
Conversion.ToOctal(data),
task,
UCodeMemory.GetBank(task));
}
/// <summary>
/// Specifies the range (or ranges) of addresses decoded by this device.
/// </summary>
public MemoryRange[] Addresses
{
get { return _addresses; }
}
/// <summary>
/// From: http://bitsavers.org/pdf/xerox/alto/memos_1975/Reserved_Alto_Memory_Locations_Jan75.pdf
///
/// #177140 - #177157: Organ Keyboard (Organ Hardware - Kaehler)
/// </summary>
private readonly MemoryRange[] _addresses =
{
new MemoryRange(0xfe60, 0xfe6f),
};
private ushort[] _keyData = new ushort[16];
private AltoSystem _system;
}
}

View File

@ -45,7 +45,10 @@ namespace Contralto.Logging
HostNetworkInterface = 0x2000,
EthernetPacket = 0x4000,
Configuration = 0x8000,
Music = 0x10000,
DAC = 0x10000,
Organ = 0x20000,
Orbit = 0x40000,
DoverROS = 0x80000,
Debug = 0x40000000,
All = 0x7fffffff
@ -73,7 +76,7 @@ namespace Contralto.Logging
static Log()
{
// TODO: make configurable
_components = LogComponent.None; // LogComponent.HostNetworkInterface | LogComponent.EthernetPacket; // | LogComponent.HostEthernet | LogComponent.EthernetController; // LogComponent.DiskController | LogComponent.DiskSectorTask | LogComponent.Debug | LogComponent.CPU; // LogComponent.EthernetController; // | LogComponent.Microcode | LogComponent.Memory | LogComponent.CPU;
_components = LogComponent.DAC;
_type = LogType.Normal | LogType.Warning | LogType.Error | LogType.Verbose;
//_logStream = new StreamWriter("log.txt");

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.*")]
[assembly: AssemblyVersion("1.1.1")]
[assembly: AssemblyFileVersion("1.1.0.0")]

View File

@ -12,7 +12,7 @@ namespace Contralto.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -166,5 +166,41 @@ namespace Contralto.Properties {
this["EnableWindowsFormsHighDpiAutoResizing"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool EnableAudioDAC {
get {
return ((bool)(this["EnableAudioDAC"]));
}
set {
this["EnableAudioDAC"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool EnableAudioDACCapture {
get {
return ((bool)(this["EnableAudioDACCapture"]));
}
set {
this["EnableAudioDACCapture"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string AudioDACCapturePath {
get {
return ((string)(this["AudioDACCapturePath"]));
}
set {
this["AudioDACCapturePath"] = value;
}
}
}
}

View File

@ -38,5 +38,14 @@
<Setting Name="EnableWindowsFormsHighDpiAutoResizing" Type="System.String" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="EnableAudioDAC" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="EnableAudioDACCapture" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="AudioDACCapturePath" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>

View File

@ -125,7 +125,7 @@ namespace Contralto
_otherRegs.Rows[1].Cells[1].Value = Conversion.ToOctal(_system.CPU.T, 6);
_otherRegs.Rows[2].Cells[1].Value = Conversion.ToOctal(_system.CPU.M, 6);
_otherRegs.Rows[3].Cells[1].Value = Conversion.ToOctal(_system.CPU.IR, 6);
_otherRegs.Rows[4].Cells[1].Value = Conversion.ToOctal(_system.CPU.ALUC0, 1);
_otherRegs.Rows[4].Cells[1].Value = Conversion.ToOctal(_system.CPU.ALUC0, 1);
_otherRegs.Rows[5].Cells[1].Value = Conversion.ToOctal(_system.MemoryBus.MAR, 6);
_otherRegs.Rows[6].Cells[1].Value = Conversion.ToOctal(_system.MemoryBus.MDLow, 6);
_otherRegs.Rows[7].Cells[1].Value = Conversion.ToOctal(_system.MemoryBus.MDHigh, 6);
@ -241,13 +241,13 @@ namespace Contralto
_memoryData.RowCount = 65536;
_ram0SourceViewer.RowCount = 1024;
_ram1SourceViewer.RowCount = 1024;
_ram2SourceViewer.RowCount = 1024;
_ram2SourceViewer.RowCount = 1024;
_otherRegs.Rows.Add("L", "0");
_otherRegs.Rows.Add("T", "0");
_otherRegs.Rows.Add("M", "0");
_otherRegs.Rows.Add("IR", "0");
_otherRegs.Rows.Add("ALUC0", "0");
_otherRegs.Rows.Add("ALUC0", "0");
_otherRegs.Rows.Add("MAR", "0");
_otherRegs.Rows.Add("←MDL", "0");
_otherRegs.Rows.Add("←MDH", "0");
@ -279,7 +279,7 @@ namespace Contralto
sb.AppendFormat("{0}:{1} {2}\r\n",
Conversion.ToOctal(i, 6),
_memoryData.Rows[i].Cells[2].Value,
_memoryData.Rows[i].Cells[3].Value);
_memoryData.Rows[i].Cells[3].Value);
}
Clipboard.SetText(sb.ToString());
@ -583,7 +583,7 @@ namespace Contralto
string[] taskText =
{
"EM", // 0 - emulator
String.Empty,
"OR", // 1 - orbit
String.Empty,
String.Empty,
"KS", // 4 - disk sector
@ -615,7 +615,7 @@ namespace Contralto
Color[] taskColors =
{
Color.LightBlue, // 0 - emulator
Color.LightGray, // 1 - unused
Color.LightGoldenrodYellow, // 1 - orbit
Color.LightGray, // 2 - unused
Color.LightGray, // 3 - unused
Color.LightGreen, // 4 - disk sector
@ -673,6 +673,10 @@ namespace Contralto
Task = TaskType.Emulator;
break;
case "OR":
Task = TaskType.Orbit;
break;
case "SE":
Task = TaskType.DiskSector;
break;

View File

@ -30,6 +30,7 @@
{
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.AltoI1KROMRadioButton = new System.Windows.Forms.RadioButton();
this.AltoII3KRAMRadioButton = new System.Windows.Forms.RadioButton();
this.AltoII2KROMRadioButton = new System.Windows.Forms.RadioButton();
this.label1 = new System.Windows.Forms.Label();
@ -49,13 +50,21 @@
this.InterlaceDisplayCheckBox = new System.Windows.Forms.CheckBox();
this.DialogOKButton = new System.Windows.Forms.Button();
this.DialogCancelButton = new System.Windows.Forms.Button();
this.AltoI1KROMRadioButton = new System.Windows.Forms.RadioButton();
this.tabPage4 = new System.Windows.Forms.TabPage();
this.EnableDACCheckBox = new System.Windows.Forms.CheckBox();
this.DACOptionsGroupBox = new System.Windows.Forms.GroupBox();
this.EnableDACCaptureCheckBox = new System.Windows.Forms.CheckBox();
this.label2 = new System.Windows.Forms.Label();
this.DACOutputCapturePathTextBox = new System.Windows.Forms.TextBox();
this.BrowseButton = new System.Windows.Forms.Button();
this.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout();
this.tabPage2.SuspendLayout();
this.groupBox1.SuspendLayout();
this.HostInterfaceGroupBox.SuspendLayout();
this.tabPage3.SuspendLayout();
this.tabPage4.SuspendLayout();
this.DACOptionsGroupBox.SuspendLayout();
this.SuspendLayout();
//
// tabControl1
@ -63,6 +72,7 @@
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Controls.Add(this.tabPage3);
this.tabControl1.Controls.Add(this.tabPage4);
this.tabControl1.Location = new System.Drawing.Point(3, 5);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
@ -84,6 +94,17 @@
this.tabPage1.Text = "CPU";
this.tabPage1.UseVisualStyleBackColor = true;
//
// AltoI1KROMRadioButton
//
this.AltoI1KROMRadioButton.AutoSize = true;
this.AltoI1KROMRadioButton.Location = new System.Drawing.Point(14, 30);
this.AltoI1KROMRadioButton.Name = "AltoI1KROMRadioButton";
this.AltoI1KROMRadioButton.Size = new System.Drawing.Size(214, 17);
this.AltoI1KROMRadioButton.TabIndex = 4;
this.AltoI1KROMRadioButton.TabStop = true;
this.AltoI1KROMRadioButton.Text = "Alto I, 1K Control ROM, 1K Control RAM";
this.AltoI1KROMRadioButton.UseVisualStyleBackColor = true;
//
// AltoII3KRAMRadioButton
//
this.AltoII3KRAMRadioButton.AutoSize = true;
@ -266,36 +287,98 @@
this.InterlaceDisplayCheckBox.Text = "Interlaced Display (headache mode)";
this.InterlaceDisplayCheckBox.UseVisualStyleBackColor = true;
//
// OKButton
// DialogOKButton
//
this.DialogOKButton.Location = new System.Drawing.Point(211, 239);
this.DialogOKButton.Name = "OKButton";
this.DialogOKButton.Name = "DialogOKButton";
this.DialogOKButton.Size = new System.Drawing.Size(75, 23);
this.DialogOKButton.TabIndex = 1;
this.DialogOKButton.Text = "OK";
this.DialogOKButton.UseVisualStyleBackColor = true;
this.DialogOKButton.Click += new System.EventHandler(this.OKButton_Click);
//
// CancelButton
// DialogCancelButton
//
this.DialogCancelButton.Location = new System.Drawing.Point(292, 239);
this.DialogCancelButton.Name = "CancelButton";
this.DialogCancelButton.Name = "DialogCancelButton";
this.DialogCancelButton.Size = new System.Drawing.Size(75, 23);
this.DialogCancelButton.TabIndex = 2;
this.DialogCancelButton.Text = "Cancel";
this.DialogCancelButton.UseVisualStyleBackColor = true;
this.DialogCancelButton.Click += new System.EventHandler(this.CancelButton_Click);
//
// AltoI1KROMRadioButton
// tabPage4
//
this.AltoI1KROMRadioButton.AutoSize = true;
this.AltoI1KROMRadioButton.Location = new System.Drawing.Point(14, 30);
this.AltoI1KROMRadioButton.Name = "AltoI1KROMRadioButton";
this.AltoI1KROMRadioButton.Size = new System.Drawing.Size(214, 17);
this.AltoI1KROMRadioButton.TabIndex = 4;
this.AltoI1KROMRadioButton.TabStop = true;
this.AltoI1KROMRadioButton.Text = "Alto I, 1K Control ROM, 1K Control RAM";
this.AltoI1KROMRadioButton.UseVisualStyleBackColor = true;
this.tabPage4.Controls.Add(this.DACOptionsGroupBox);
this.tabPage4.Controls.Add(this.EnableDACCheckBox);
this.tabPage4.Location = new System.Drawing.Point(4, 22);
this.tabPage4.Name = "tabPage4";
this.tabPage4.Padding = new System.Windows.Forms.Padding(3);
this.tabPage4.Size = new System.Drawing.Size(360, 201);
this.tabPage4.TabIndex = 3;
this.tabPage4.Text = "DAC";
this.tabPage4.UseVisualStyleBackColor = true;
//
// EnableDACCheckBox
//
this.EnableDACCheckBox.AutoSize = true;
this.EnableDACCheckBox.Location = new System.Drawing.Point(19, 22);
this.EnableDACCheckBox.Name = "EnableDACCheckBox";
this.EnableDACCheckBox.Size = new System.Drawing.Size(275, 17);
this.EnableDACCheckBox.TabIndex = 0;
this.EnableDACCheckBox.Text = "Enable Audio DAC (Used by Smalltalk Music System)";
this.EnableDACCheckBox.UseVisualStyleBackColor = true;
this.EnableDACCheckBox.CheckedChanged += new System.EventHandler(this.OnEnableDACCheckboxChanged);
//
// DACOptionsGroupBox
//
this.DACOptionsGroupBox.Controls.Add(this.BrowseButton);
this.DACOptionsGroupBox.Controls.Add(this.DACOutputCapturePathTextBox);
this.DACOptionsGroupBox.Controls.Add(this.label2);
this.DACOptionsGroupBox.Controls.Add(this.EnableDACCaptureCheckBox);
this.DACOptionsGroupBox.Location = new System.Drawing.Point(15, 52);
this.DACOptionsGroupBox.Name = "DACOptionsGroupBox";
this.DACOptionsGroupBox.Size = new System.Drawing.Size(335, 139);
this.DACOptionsGroupBox.TabIndex = 1;
this.DACOptionsGroupBox.TabStop = false;
this.DACOptionsGroupBox.Text = "DAC options";
//
// EnableDACCaptureCheckBox
//
this.EnableDACCaptureCheckBox.AutoSize = true;
this.EnableDACCaptureCheckBox.Location = new System.Drawing.Point(18, 28);
this.EnableDACCaptureCheckBox.Name = "EnableDACCaptureCheckBox";
this.EnableDACCaptureCheckBox.Size = new System.Drawing.Size(156, 17);
this.EnableDACCaptureCheckBox.TabIndex = 0;
this.EnableDACCaptureCheckBox.Text = "Enable DAC output capture";
this.EnableDACCaptureCheckBox.UseVisualStyleBackColor = true;
this.EnableDACCaptureCheckBox.CheckedChanged += new System.EventHandler(this.EnableDACCaptureCheckBox_CheckedChanged);
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(16, 58);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(105, 13);
this.label2.TabIndex = 1;
this.label2.Text = "Output capture path:";
//
// DACOutputCapturePathTextBox
//
this.DACOutputCapturePathTextBox.Location = new System.Drawing.Point(127, 55);
this.DACOutputCapturePathTextBox.Name = "DACOutputCapturePathTextBox";
this.DACOutputCapturePathTextBox.Size = new System.Drawing.Size(110, 20);
this.DACOutputCapturePathTextBox.TabIndex = 2;
//
// BrowseButton
//
this.BrowseButton.Location = new System.Drawing.Point(251, 53);
this.BrowseButton.Name = "BrowseButton";
this.BrowseButton.Size = new System.Drawing.Size(75, 23);
this.BrowseButton.TabIndex = 3;
this.BrowseButton.Text = "Browse...";
this.BrowseButton.UseVisualStyleBackColor = true;
this.BrowseButton.Click += new System.EventHandler(this.BrowseButton_Click);
//
// SystemOptions
//
@ -320,6 +403,10 @@
this.HostInterfaceGroupBox.PerformLayout();
this.tabPage3.ResumeLayout(false);
this.tabPage3.PerformLayout();
this.tabPage4.ResumeLayout(false);
this.tabPage4.PerformLayout();
this.DACOptionsGroupBox.ResumeLayout(false);
this.DACOptionsGroupBox.PerformLayout();
this.ResumeLayout(false);
}
@ -348,5 +435,12 @@
private System.Windows.Forms.RadioButton UDPRadioButton;
private System.Windows.Forms.RadioButton NoEncapsulationRadioButton;
private System.Windows.Forms.RadioButton AltoI1KROMRadioButton;
private System.Windows.Forms.TabPage tabPage4;
private System.Windows.Forms.GroupBox DACOptionsGroupBox;
private System.Windows.Forms.CheckBox EnableDACCheckBox;
private System.Windows.Forms.CheckBox EnableDACCaptureCheckBox;
private System.Windows.Forms.Button BrowseButton;
private System.Windows.Forms.TextBox DACOutputCapturePathTextBox;
private System.Windows.Forms.Label label2;
}
}

View File

@ -20,6 +20,7 @@ using PcapDotNet.Core;
using PcapDotNet.Core.Extensions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.NetworkInformation;
using System.Windows.Forms;
@ -40,6 +41,9 @@ namespace Contralto.UI
/// </summary>
private void PopulateUI()
{
//
// System Tab
//
_selectedSystemType = Configuration.SystemType;
_selectedInterfaceType = Configuration.HostPacketInterfaceType;
@ -62,9 +66,15 @@ namespace Contralto.UI
break;
}
//
// Display Tab
//
InterlaceDisplayCheckBox.Checked = Configuration.InterlaceDisplay;
ThrottleSpeedCheckBox.Checked = Configuration.ThrottleSpeed;
//
// Ethernet Tab
//
AltoEthernetAddressTextBox.Text = Conversion.ToOctal(Configuration.HostAddress);
if (!Configuration.HostRawEthernetInterfacesAvailable)
@ -94,7 +104,16 @@ namespace Contralto.UI
break;
}
PopulateNetworkAdapterList(Configuration.HostPacketInterfaceType);
PopulateNetworkAdapterList(Configuration.HostPacketInterfaceType);
//
// DAC Tab
//
EnableDACCheckBox.Checked = Configuration.EnableAudioDAC;
DACOptionsGroupBox.Enabled = Configuration.EnableAudioDAC;
DACOutputCapturePathTextBox.Text = Configuration.AudioDACCapturePath;
EnableDACCaptureCheckBox.Checked = Configuration.EnableAudioDACCapture;
}
private void PopulateNetworkAdapterList(PacketInterfaceType encapType)
@ -249,7 +268,18 @@ namespace Contralto.UI
// Display
Configuration.InterlaceDisplay = InterlaceDisplayCheckBox.Checked;
Configuration.ThrottleSpeed = ThrottleSpeedCheckBox.Checked;
Configuration.ThrottleSpeed = ThrottleSpeedCheckBox.Checked;
// DAC
Configuration.EnableAudioDAC = EnableDACCheckBox.Checked;
Configuration.EnableAudioDACCapture = EnableDACCaptureCheckBox.Checked;
Configuration.AudioDACCapturePath = DACOutputCapturePathTextBox.Text;
// Validate that the output folder exists, if not, warn the user
if (Configuration.EnableAudioDACCapture && !Directory.Exists(Configuration.AudioDACCapturePath))
{
MessageBox.Show("Warning: the specified DAC output capture path does not exist or is inaccessible.");
}
this.Close();
@ -263,6 +293,37 @@ namespace Contralto.UI
private PacketInterfaceType _selectedInterfaceType;
private SystemType _selectedSystemType;
private void BrowseButton_Click(object sender, EventArgs e)
{
BrowseForDACOutputFolder();
}
private void BrowseForDACOutputFolder()
{
FolderBrowserDialog folderDialog = new FolderBrowserDialog();
folderDialog.Description = "Choose a folder to store captured DAC audio.";
folderDialog.ShowNewFolderButton = true;
if (folderDialog.ShowDialog() == DialogResult.OK)
{
DACOutputCapturePathTextBox.Text = folderDialog.SelectedPath;
}
}
private void OnEnableDACCheckboxChanged(object sender, EventArgs e)
{
DACOptionsGroupBox.Enabled = Configuration.EnableAudioDAC;
}
private void EnableDACCaptureCheckBox_CheckedChanged(object sender, EventArgs e)
{
if (EnableDACCaptureCheckBox.Checked == true && String.IsNullOrWhiteSpace(DACOutputCapturePathTextBox.Text))
{
//
// When enabled and no output folder is set, force the user to choose an output folder.
//
BrowseForDACOutputFolder();
}
}
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NAudio" version="1.8.0" targetFramework="net452" />
</packages>

View File

@ -107,6 +107,9 @@
<File Source="$(var.Contralto.TargetDir)\PcapDotNet.Core.dll"/>
<File Source="$(var.Contralto.TargetDir)\PcapDotNet.Core.Extensions.dll"/>
<File Source="$(var.Contralto.TargetDir)\PcapDotNet.Packets.dll"/>
<!-- NAudio libs -->
<File Source="$(var.Contralto.TargetDir)\NAudio.dll"/>
</Component>
<Component Id="ContraltoReadme" Guid="0AF4F077-3858-4CEA-A3CD-CF8585F98AAB">
<File Id="readme" Name="readme.txt" Source="$(var.Contralto.TargetDir)\readme.txt">