add NES music ripper tool prototype

This commit is contained in:
zeromus 2015-05-08 00:56:46 +00:00
parent 64741e8973
commit 6833a24638
8 changed files with 1325 additions and 342 deletions

View File

@ -853,6 +853,12 @@
<Compile Include="tools\NES\NESGameGenie.Designer.cs">
<DependentUpon>NESGameGenie.cs</DependentUpon>
</Compile>
<Compile Include="tools\NES\NESMusicRipper.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="tools\NES\NESMusicRipper.Designer.cs">
<DependentUpon>NESMusicRipper.cs</DependentUpon>
</Compile>
<Compile Include="tools\NES\NESNameTableViewer.cs">
<SubType>Form</SubType>
</Compile>
@ -1378,6 +1384,9 @@
<EmbeddedResource Include="tools\NES\NESGameGenie.resx">
<DependentUpon>NESGameGenie.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="tools\NES\NESMusicRipper.resx">
<DependentUpon>NESMusicRipper.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="tools\NES\NESNameTableViewer.resx">
<DependentUpon>NESNameTableViewer.cs</DependentUpon>
</EmbeddedResource>

File diff suppressed because it is too large Load Diff

View File

@ -1239,6 +1239,11 @@ namespace BizHawk.Client.EmuHawk
GlobalWin.Tools.Load<NESNameTableViewer>();
}
private void musicRipperToolStripMenuItem_Click(object sender, EventArgs e)
{
GlobalWin.Tools.Load<NESMusicRipper>();
}
private void NESGameGenieCodesMenuItem_Click(object sender, EventArgs e)
{
GlobalWin.Tools.LoadGameGenieEc();

View File

@ -3728,5 +3728,6 @@ namespace BizHawk.Client.EmuHawk
{
GlobalWin.Tools.Load<MultiDiskBundler>();
}
}
}

View File

@ -0,0 +1,223 @@
namespace BizHawk.Client.EmuHawk
{
partial class NESMusicRipper
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NESMusicRipper));
this.btnControl = new System.Windows.Forms.Button();
this.txtDivider = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.btnExport = new System.Windows.Forms.Button();
this.lblContents = new System.Windows.Forms.Label();
this.textBox1 = new System.Windows.Forms.TextBox();
this.txtPatternLength = new System.Windows.Forms.TextBox();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.label2 = new System.Windows.Forms.Label();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.menuStrip1 = new MenuStripEx();
this.FileSubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.ExitMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout();
this.menuStrip1.SuspendLayout();
this.SuspendLayout();
//
// btnControl
//
this.btnControl.Location = new System.Drawing.Point(6, 57);
this.btnControl.Name = "btnControl";
this.btnControl.Size = new System.Drawing.Size(75, 23);
this.btnControl.TabIndex = 0;
this.btnControl.Text = "Start";
this.btnControl.UseVisualStyleBackColor = true;
this.btnControl.Click += new System.EventHandler(this.btnControl_Click);
//
// txtDivider
//
this.txtDivider.Location = new System.Drawing.Point(9, 32);
this.txtDivider.Name = "txtDivider";
this.txtDivider.Size = new System.Drawing.Size(100, 20);
this.txtDivider.TabIndex = 1;
this.txtDivider.Text = "29824";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(6, 16);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(135, 13);
this.label1.TabIndex = 2;
this.label1.Text = "APU Divider (trace interval)";
//
// btnExport
//
this.btnExport.AutoSize = true;
this.btnExport.Location = new System.Drawing.Point(6, 118);
this.btnExport.Name = "btnExport";
this.btnExport.Size = new System.Drawing.Size(100, 23);
this.btnExport.TabIndex = 3;
this.btnExport.Text = "Export XRNS File";
this.btnExport.UseVisualStyleBackColor = true;
this.btnExport.Click += new System.EventHandler(this.btnExport_Click);
//
// lblContents
//
this.lblContents.AutoSize = true;
this.lblContents.Location = new System.Drawing.Point(6, 102);
this.lblContents.Name = "lblContents";
this.lblContents.Size = new System.Drawing.Size(55, 13);
this.lblContents.TabIndex = 4;
this.lblContents.Text = "(Contents)";
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(12, 211);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.ReadOnly = true;
this.textBox1.Size = new System.Drawing.Size(390, 80);
this.textBox1.TabIndex = 6;
this.textBox1.Text = resources.GetString("textBox1.Text");
//
// txtPatternLength
//
this.txtPatternLength.Location = new System.Drawing.Point(12, 37);
this.txtPatternLength.Name = "txtPatternLength";
this.txtPatternLength.Size = new System.Drawing.Size(100, 20);
this.txtPatternLength.TabIndex = 7;
this.txtPatternLength.Text = "512";
//
// groupBox1
//
this.groupBox1.Controls.Add(this.label2);
this.groupBox1.Controls.Add(this.txtPatternLength);
this.groupBox1.Location = new System.Drawing.Point(0, 27);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(200, 156);
this.groupBox1.TabIndex = 8;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Config";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 21);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(126, 13);
this.label2.TabIndex = 8;
this.label2.Text = "Pattern Length (512 max)";
//
// groupBox2
//
this.groupBox2.Controls.Add(this.btnControl);
this.groupBox2.Controls.Add(this.txtDivider);
this.groupBox2.Controls.Add(this.btnExport);
this.groupBox2.Controls.Add(this.lblContents);
this.groupBox2.Controls.Add(this.label1);
this.groupBox2.Location = new System.Drawing.Point(206, 32);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(200, 151);
this.groupBox2.TabIndex = 9;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Log Control";
//
// menuStrip1
//
this.menuStrip1.ClickThrough = true;
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.FileSubMenu});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(437, 24);
this.menuStrip1.TabIndex = 5;
this.menuStrip1.Text = "menuStrip1";
//
// FileSubMenu
//
this.FileSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripSeparator2,
this.ExitMenuItem});
this.FileSubMenu.Name = "FileSubMenu";
this.FileSubMenu.Size = new System.Drawing.Size(35, 20);
this.FileSubMenu.Text = "&File";
//
// toolStripSeparator2
//
this.toolStripSeparator2.Name = "toolStripSeparator2";
this.toolStripSeparator2.Size = new System.Drawing.Size(129, 6);
//
// ExitMenuItem
//
this.ExitMenuItem.Name = "ExitMenuItem";
this.ExitMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Alt | System.Windows.Forms.Keys.F4)));
this.ExitMenuItem.Size = new System.Drawing.Size(132, 22);
this.ExitMenuItem.Text = "E&xit";
this.ExitMenuItem.Click += new System.EventHandler(this.ExitMenuItem_Click);
//
// NESMusicRipper
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(437, 305);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.menuStrip1);
this.Name = "NESMusicRipper";
this.Text = "NESMusicRipper";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.NESMusicRipper_FormClosed);
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btnControl;
private System.Windows.Forms.TextBox txtDivider;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button btnExport;
private System.Windows.Forms.Label lblContents;
private MenuStripEx menuStrip1;
private System.Windows.Forms.ToolStripMenuItem FileSubMenu;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
private System.Windows.Forms.ToolStripMenuItem ExitMenuItem;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.TextBox txtPatternLength;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.GroupBox groupBox2;
}
}

View File

@ -0,0 +1,523 @@
using System;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Linq;
using System.Linq;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Emulation.Cores.Nintendo.NES;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.EmuHawk
{
public partial class NESMusicRipper : Form, IToolFormAutoConfig
{
[RequiredService]
private IEmulator _emu { get; set; }
public NESMusicRipper()
{
InitializeComponent();
SyncContents();
}
public bool AskSaveChanges() { return true; }
public bool UpdateBefore { get { return true; } }
public void Restart()
{
}
public void UpdateValues()
{
}
public void FastUpdate()
{
// Do nothing
}
private void RefreshFloatingWindowControl()
{
}
bool IsRunning;
protected override void OnShown(EventArgs e)
{
RefreshFloatingWindowControl();
base.OnShown(e);
}
//http://www.phy.mtu.edu/~suits/notefreqs.html
//begins at C0. ends at B8
static readonly float[] freqtbl = new[] {0,
16.35f,17.32f,18.35f,19.45f,20.6f,21.83f,23.12f,24.5f,25.96f,27.5f,29.14f,30.87f,32.7f,34.65f,36.71f,38.89f,41.2f,43.65f,46.25f,49f,51.91f,55f,58.27f,61.74f,65.41f,69.3f,73.42f,77.78f,82.41f,87.31f,92.5f,98f,103.83f,110f,116.54f,123.47f,130.81f,138.59f,146.83f,155.56f,164.81f,174.61f,185f,196f,207.65f,220f,233.08f,246.94f,261.63f,277.18f,293.66f,311.13f,329.63f,349.23f,369.99f,392f,415.3f,440f,466.16f,493.88f,523.25f,554.37f,587.33f,622.25f,659.25f,698.46f,739.99f,783.99f,830.61f,880f,932.33f,987.77f,1046.5f,1108.73f,1174.66f,1244.51f,1318.51f,1396.91f,1479.98f,1567.98f,1661.22f,1760f,1864.66f,1975.53f,2093f,2217.46f,2349.32f,2489.02f,2637.02f,2793.83f,2959.96f,3135.96f,3322.44f,3520f,3729.31f,3951.07f,4186.01f,4434.92f,4698.63f,4978.03f,5274.04f,5587.65f,5919.91f,6271.93f,6644.88f,7040f,7458.62f,7902.13f,
1000000
};
static readonly string[] noteNames = new[] { "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" };
string NameForNote(int note)
{
int tone = note % 12;
int octave = note / 12;
return string.Format("{0}{1}", noteNames[tone], octave);
}
//this isnt thoroughly debugged but it seems to work OK
//pitch bends are massively broken anyway
int FindNearestNote(float freq)
{
for (int i = 1; i < freqtbl.Length; i++)
{
float a = freqtbl[i - 1];
float b = freqtbl[i];
float c = freqtbl[i + 1];
float min = (a + b) / 2;
float max = (b + c) / 2;
if (freq >= min && freq <= max)
return i - 1;
}
return 95; //I guess?
}
struct PulseState
{
public bool en;
public byte vol, type;
public int note;
}
struct TriangleState
{
public bool en;
public int note;
}
struct NoiseState
{
public bool en;
public byte vol;
public int note;
}
class ApuState
{
public PulseState pulse0, pulse1;
public TriangleState triangle;
public NoiseState noise;
}
class Stupid : ICSharpCode.SharpZipLib.Zip.IStaticDataSource
{
public Stream stream;
public Stream GetSource() { return stream; }
}
private void btnExport_Click(object sender, EventArgs e)
{
//acquire target
var sfd = new SaveFileDialog();
sfd.Filter = "XRNS (*.xrns)|*.xrns";
if (sfd.ShowDialog() != System.Windows.Forms.DialogResult.OK)
return;
//configuration:
var outPath = sfd.FileName;
string templatePath = Path.Combine(Path.GetDirectoryName(outPath), "template.xrns");
int configuredPatternLength = int.Parse(txtPatternLength.Text);
//load template
MemoryStream msSongXml = new MemoryStream();
var zfTemplate = new ICSharpCode.SharpZipLib.Zip.ZipFile(templatePath);
{
int zfSongXmlIndex = zfTemplate.FindEntry("Song.xml", true);
using (var zis = zfTemplate.GetInputStream(zfTemplate.GetEntry("Song.xml")))
{
byte[] buffer = new byte[4096]; // 4K is optimum
ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(zis, msSongXml, buffer);
}
}
XElement templateRoot = XElement.Parse(System.Text.Encoding.UTF8.GetString(msSongXml.ToArray()));
//get the pattern pool, and whack the child nodes
var xPatterns = templateRoot.XPathSelectElement("//Patterns");
var xPatternPool = xPatterns.Parent;
xPatterns.Remove();
var writer = new StringWriter();
writer.WriteLine("<Patterns>");
int pulse0_lastNote = -1;
int pulse0_lastType = -1;
int pulse1_lastNote = -1;
int pulse1_lastType = -1;
int tri_lastNote = -1;
int noise_lastNote = -1;
int patternCount = 0;
int time = 0;
while (time < Log.Count)
{
patternCount++;
//begin writing pattern: open the tracks list
writer.WriteLine("<Pattern>");
writer.WriteLine("<NumberOfLines>{0}</NumberOfLines>", configuredPatternLength);
writer.WriteLine("<Tracks>");
//write the pulse tracks
for (int TRACK = 0; TRACK < 2; TRACK++)
{
writer.WriteLine("<PatternTrack type=\"PatternTrack\">");
writer.WriteLine("<Lines>");
int lastNote = TRACK == 0 ? pulse0_lastNote : pulse1_lastNote;
int lastType = TRACK == 0 ? pulse0_lastType : pulse1_lastType;
for (int i = 0; i < configuredPatternLength; i++)
{
int patLine = i;
int index = i + time;
if (index >= Log.Count) continue;
var rec = Log[index];
PulseState pulse = new PulseState();
if (TRACK == 0) pulse = rec.pulse0;
if (TRACK == 1) pulse = rec.pulse1;
//transform quieted notes to dead notes
//blech its buggy, im tired
//if (pulse.vol == 0)
// pulse.en = false;
bool keyoff = false, keyon = false;
if (lastNote != -1 && !pulse.en)
{
lastNote = -1;
lastType = -1;
keyoff = true;
}
else if (lastNote != pulse.note && pulse.en)
keyon = true;
if (lastType != pulse.type && pulse.note != -1)
keyon = true;
if (pulse.en)
{
lastNote = pulse.note;
lastType = pulse.type;
}
writer.WriteLine("<Line index=\"{0}\">", patLine);
writer.WriteLine("<NoteColumns>");
writer.WriteLine("<NoteColumn>");
if (keyon)
{
writer.WriteLine("<Note>{0}</Note>", NameForNote(pulse.note));
writer.WriteLine("<Instrument>{0:X2}</Instrument>", pulse.type);
}
else if (keyoff) writer.WriteLine("<Note>OFF</Note>");
if(lastNote != -1)
writer.WriteLine("<Volume>{0:X2}</Volume>", pulse.vol * 8);
writer.WriteLine("</NoteColumn>");
writer.WriteLine("</NoteColumns>");
writer.WriteLine("</Line>");
}
//close PatternTrack
writer.WriteLine("</Lines>");
writer.WriteLine("</PatternTrack>");
if (TRACK == 0)
{
pulse0_lastNote = lastNote;
pulse0_lastType = lastType;
}
else
{
pulse1_lastNote = lastNote;
pulse1_lastType = lastType;
}
} //pulse tracks loop
//triangle track generation
{
writer.WriteLine("<PatternTrack type=\"PatternTrack\">");
writer.WriteLine("<Lines>");
for (int i = 0; i < configuredPatternLength; i++)
{
int patLine = i;
int index = i + time;
if (index >= Log.Count) continue;
var rec = Log[index];
TriangleState tri = rec.triangle;
{
bool keyoff = false, keyon = false;
if (tri_lastNote != -1 && !tri.en)
{
tri_lastNote = -1;
keyoff = true;
}
else if (tri_lastNote != tri.note && tri.en)
keyon = true;
if(tri.en)
tri_lastNote = tri.note;
writer.WriteLine("<Line index=\"{0}\">", patLine);
writer.WriteLine("<NoteColumns>");
writer.WriteLine("<NoteColumn>");
if (keyon)
{
writer.WriteLine("<Note>{0}</Note>", NameForNote(tri.note));
writer.WriteLine("<Instrument>08</Instrument>");
}
else if (keyoff) writer.WriteLine("<Note>OFF</Note>");
//no need for tons of these
//if(keyon) writer.WriteLine("<Volume>80</Volume>");
writer.WriteLine("</NoteColumn>");
writer.WriteLine("</NoteColumns>");
writer.WriteLine("</Line>");
}
}
//close PatternTrack
writer.WriteLine("</Lines>");
writer.WriteLine("</PatternTrack>");
}
//noise track generation
{
writer.WriteLine("<PatternTrack type=\"PatternTrack\">");
writer.WriteLine("<Lines>");
for (int i = 0; i < configuredPatternLength; i++)
{
int patLine = i;
int index = i + time;
if (index >= Log.Count) continue;
var rec = Log[index];
NoiseState noise = rec.noise;
//transform quieted notes to dead notes
//blech its buggy, im tired
//if (noise.vol == 0)
// noise.en = false;
{
bool keyoff = false, keyon = false;
if (noise_lastNote != -1 && !noise.en)
{
noise_lastNote = -1;
keyoff = true;
}
else if (noise_lastNote != noise.note && noise.en)
keyon = true;
if (noise.en)
noise_lastNote = noise.note;
writer.WriteLine("<Line index=\"{0}\">", patLine);
writer.WriteLine("<NoteColumns>");
writer.WriteLine("<NoteColumn>");
if (keyon)
{
writer.WriteLine("<Note>{0}</Note>", NameForNote(noise.note));
writer.WriteLine("<Instrument>04</Instrument>");
}
else if (keyoff) writer.WriteLine("<Note>OFF</Note>");
if (noise_lastNote != -1)
writer.WriteLine("<Volume>{0:X2}</Volume>", noise.vol * 8);
writer.WriteLine("</NoteColumn>");
writer.WriteLine("</NoteColumns>");
writer.WriteLine("</Line>");
}
}
//close PatternTrack
writer.WriteLine("</Lines>");
writer.WriteLine("</PatternTrack>");
} //noise track generation
//write empty track for now for pcm
for (int TRACK = 4; TRACK < 5; TRACK++)
{
writer.WriteLine("<PatternTrack type=\"PatternTrack\">");
writer.WriteLine("<Lines>");
writer.WriteLine("</Lines>");
writer.WriteLine("</PatternTrack>");
}
//we definitely need a dummy master track now
writer.WriteLine("<PatternMasterTrack type=\"PatternMasterTrack\">");
writer.WriteLine("</PatternMasterTrack>");
//close tracks
writer.WriteLine("</Tracks>");
//close pattern
writer.WriteLine("</Pattern>");
time += configuredPatternLength;
} //main pattern loop
writer.WriteLine("</Patterns>");
writer.Flush();
var xNewPatternList = XElement.Parse(writer.ToString());
xPatternPool.Add(xNewPatternList);
//write pattern sequence
writer = new StringWriter();
writer.WriteLine("<SequenceEntries>");
for (int i = 0; i < patternCount; i++)
{
writer.WriteLine("<SequenceEntry>");
writer.WriteLine("<IsSectionStart>false</IsSectionStart>");
writer.WriteLine("<Pattern>{0}</Pattern>", i);
writer.WriteLine("</SequenceEntry>");
}
writer.WriteLine("</SequenceEntries>");
var xPatternSequence = templateRoot.XPathSelectElement("//PatternSequence");
xPatternSequence.XPathSelectElement("SequenceEntries").Remove();
xPatternSequence.Add(XElement.Parse(writer.ToString()));
//copy template file to target
File.Delete(outPath);
File.Copy(templatePath, outPath);
var msOutXml = new MemoryStream();
templateRoot.Save(msOutXml);
msOutXml.Flush();
msOutXml.Position = 0;
var zfOutput = new ICSharpCode.SharpZipLib.Zip.ZipFile(outPath);
zfOutput.BeginUpdate();
zfOutput.Add(new Stupid { stream = msOutXml }, "Song.xml");
zfOutput.CommitUpdate();
zfOutput.Close();
//for easier debugging, write patterndata XML
//DUMP_TO_DISK(msOutXml.ToArray())
}
List<ApuState> Log = new List<ApuState>();
void DebugCallback()
{
//fpulse = fCPU/(16*(t+1)) (where fCPU is 1.789773 MHz for NTSC, 1.662607 MHz for PAL, and 1.773448 MHz for Dendy)
//ftriangle = fCPU/(32*(tval + 1))
var nes = _emu as NES;
var apu = nes.apu;
//evaluate the pitches
int pulse0_period = apu.pulse[0].timer_reload_value;
float pulse0_freq = 1789773.0f / (16.0f * (pulse0_period + 1));
int pulse0_note = FindNearestNote(pulse0_freq);
int pulse1_period = apu.pulse[1].timer_reload_value;
float pulse1_freq = 1789773.0f / (16.0f * (pulse1_period + 1));
int pulse1_note = FindNearestNote(pulse1_freq);
int tri_period = apu.triangle.Debug_PeriodValue;
float tri_freq = 1789773.0f / (32.0f * (tri_period + 1));
int tri_note = FindNearestNote(tri_freq);
//uncertain
int noise_period = apu.noise.Debug_Period;
float noise_freq = 1789773.0f / (16.0f * (noise_period + 1));
int noise_note = FindNearestNote(noise_freq);
//create the record
ApuState rec = new ApuState();
rec.pulse0.en = !apu.pulse[0].Debug_IsSilenced;
rec.pulse0.vol = (byte)apu.pulse[0].Debug_Volume;
rec.pulse0.note = pulse0_note;
rec.pulse0.type = (byte)apu.pulse[0].Debug_DutyType;
rec.pulse1.en = !apu.pulse[1].Debug_IsSilenced;
rec.pulse1.vol = (byte)apu.pulse[1].Debug_Volume;
rec.pulse1.note = pulse1_note;
rec.pulse1.type = (byte)apu.pulse[1].Debug_DutyType;
rec.triangle.en = !apu.triangle.Debug_IsSilenced;
rec.triangle.note = tri_note;
rec.noise.en = !apu.noise.Debug_IsSilenced;
rec.noise.vol = (byte)apu.noise.Debug_Volume;
rec.noise.note = noise_note;
Log.Add(rec);
SyncContents();
}
void SyncContents()
{
lblContents.Text = string.Format("{0} Rows", Log.Count);
}
private void btnControl_Click(object sender, EventArgs e)
{
var nes = _emu as NES;
if(IsRunning)
{
SyncContents();
nes.apu.DebugCallback = null;
nes.apu.DebugCallbackDivider = 0;
IsRunning = false;
btnControl.Text = "Start";
}
else
{
Log.Clear();
nes.apu.DebugCallback = DebugCallback;
nes.apu.DebugCallbackDivider = int.Parse(txtDivider.Text);
IsRunning = true;
btnControl.Text = "Stop";
}
}
private void ExitMenuItem_Click(object sender, EventArgs e)
{
Close();
}
private void NESMusicRipper_FormClosed(object sender, FormClosedEventArgs e)
{
var nes = _emu as NES;
var apu = nes.apu;
apu.DebugCallbackDivider = 0;
apu.DebugCallbackTimer = 0;
apu.DebugCallback = null;
}
}
}

View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="textBox1.Text" xml:space="preserve">
<value>How to use:
Create a file called template.xrns in the directory you are going to export to. This tool will load that and replace the patterns with the logged data.
Make sure the template has 5 tracks. There should be pulse waveforms in instruments (0,1,2,3), noise in instrument 4, and a triangle in instrument 8.</value>
</data>
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@ -74,7 +74,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
};
sealed class PulseUnit
public sealed class PulseUnit
{
public PulseUnit(APU apu, int unit) { this.unit = unit; this.apu = apu; }
public int unit;
@ -87,7 +87,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
bool sweep_reload;
//reg2/3
int len_cnt;
int timer_raw_reload_value, timer_reload_value;
public int timer_raw_reload_value, timer_reload_value;
//misc..
int lenctr_en;
@ -184,7 +184,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
public int sample;
bool duty_value;
int env_start_flag, env_divider, env_counter, env_output;
int env_start_flag, env_divider, env_counter;
public int env_output;
public void clock_length_and_sweep()
{
@ -287,9 +288,35 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
sample = newsample;
}
}
public bool Debug_IsSilenced
{
get
{
if (swp_silence || len_cnt == 0)
return true;
else return false;
}
}
public int Debug_DutyType
{
get
{
return duty_cnt;
}
}
public int Debug_Volume
{
get
{
return env_output;
}
}
}
sealed class NoiseUnit
public sealed class NoiseUnit
{
APU apu;
@ -320,6 +347,31 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
NOISE_TABLE = pal ? NOISE_TABLE_PAL : NOISE_TABLE_NTSC;
}
public bool Debug_IsSilenced
{
get
{
if (len_cnt == 0) return true;
else return false;
}
}
public int Debug_Period
{
get
{
return period_cnt;
}
}
public int Debug_Volume
{
get
{
return env_output;
}
}
public void SyncState(Serializer ser)
{
ser.BeginSection("Noise");
@ -449,7 +501,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
}
}
sealed class TriangleUnit
public sealed class TriangleUnit
{
//reg0
int linear_counter_reload, control_flag;
@ -520,6 +572,23 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
//Console.WriteLine("tri timer_reload_value: {0}", timer_cnt_reload);
}
public bool Debug_IsSilenced
{
get
{
bool en = len_cnt != 0 && linear_counter != 0;
return !en;
}
}
public int Debug_PeriodValue
{
get
{
return timer_cnt;
}
}
public void Run()
{
//when clocked by timer
@ -820,9 +889,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
SyncIRQ();
}
PulseUnit[] pulse = new PulseUnit[2];
TriangleUnit triangle;
NoiseUnit noise; //= new NoiseUnit();
public PulseUnit[] pulse = new PulseUnit[2];
public TriangleUnit triangle;
public NoiseUnit noise;
DMCUnit dmc;
bool irq_pending;
@ -1034,6 +1103,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
}
}
public Action DebugCallback;
public int DebugCallbackDivider;
public int DebugCallbackTimer;
int toggle = 0;
public void RunOne()
{
@ -1079,6 +1152,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
//since the units run concurrently, the APU frame sequencer is ran last because
//it can change the ouput values of the pulse/triangle channels
//we want the changes to affect it on the *next* cycle.
if(DebugCallbackDivider != 0)
{
if(DebugCallbackTimer==0)
{
if(DebugCallback != null)
DebugCallback();
DebugCallbackTimer = DebugCallbackDivider;
} else DebugCallbackTimer--;
}
}
public struct Delta