Browse Source

commit: Spectrum background in timeline edit mode

HOME 3 years ago
parent
commit
e79a15a29c

+ 20 - 0
BeatLyrics.Tool/BeatLyrics.Tool.csproj

@@ -70,6 +70,8 @@
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Design" />
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Windows.Forms.DataVisualization" />
     <Reference Include="System.Xml.Linq" />
     <Reference Include="System.Data.DataSetExtensions" />
     <Reference Include="Microsoft.CSharp" />
@@ -82,6 +84,13 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="DataProvider\BeatMaps\LocalCustomLevelProvider.cs" />
+    <Compile Include="Dialogs\ContextDialog.cs" />
+    <Compile Include="Dialogs\PlotDialog.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Dialogs\PlotDialog.Designer.cs">
+      <DependentUpon>PlotDialog.cs</DependentUpon>
+    </Compile>
     <Compile Include="Dialogs\TextDialog.cs">
       <SubType>Form</SubType>
     </Compile>
@@ -105,6 +114,9 @@
     <Compile Include="Models\LevelInfo.cs" />
     <Compile Include="DataProvider\OnlineLyric\LyricProvider.cs" />
     <Compile Include="DataProvider\OnlineLyric\NeteaseCloudMusicLyricProvider.cs" />
+    <Compile Include="RefLibs\ZzzzRangeBar.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
     <Compile Include="UserControls\TimelineEditorUserControl.cs">
       <SubType>UserControl</SubType>
     </Compile>
@@ -143,18 +155,26 @@
     <Compile Include="Utils\BaseForm.cs">
       <SubType>Form</SubType>
     </Compile>
+    <Compile Include="RefLibs\DSPLib.cs" />
     <Compile Include="Utils\Formatter.cs" />
     <Compile Include="Utils\Hasher.cs" />
     <Compile Include="Utils\LoadingWrap.cs" />
     <Compile Include="Utils\OggAudioPlayer.cs" />
     <Compile Include="Utils\JapanesePhonetic.cs" />
     <Compile Include="Utils\JapaneseConverter.cs" />
+    <Compile Include="Utils\SpectrumAnalyzer.cs" />
+    <EmbeddedResource Include="Dialogs\PlotDialog.resx">
+      <DependentUpon>PlotDialog.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Dialogs\TextDialog.resx">
       <DependentUpon>TextDialog.cs</DependentUpon>
     </EmbeddedResource>
     <EmbeddedResource Include="Dialogs\TextEditDialog.resx">
       <DependentUpon>TextEditDialog.cs</DependentUpon>
     </EmbeddedResource>
+    <EmbeddedResource Include="RefLibs\ZzzzRangeBar.resx">
+      <DependentUpon>ZzzzRangeBar.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="UserControls\TextArrangeUserControl.resx">
       <DependentUpon>TextArrangeUserControl.cs</DependentUpon>
     </EmbeddedResource>

+ 1 - 1
BeatLyrics.Tool/DataProvider/BeatMaps/LocalCustomLevelProvider.cs

@@ -30,7 +30,7 @@ namespace BeatLyrics.Tool.DataProvider.BeatMaps
             Parallel.For(0, dirs.Length, index =>
             {
                 var dir = dirs[index];
-                var item = new LevelInfo { Directory = dir };
+                var item = new LevelInfo { Directory = dir, DirectoryTime = new DirectoryInfo(dir).CreationTime };
 
                 try
                 {

+ 36 - 9
BeatLyrics.Tool/DataProvider/OnlineLyric/NeteaseCloudMusicLyricProvider.cs

@@ -1,8 +1,8 @@
-using System;
-using System.Linq;
-using BeatLyrics.Tool.DataProvider.OnlineLyric.Models;
-using BrightIdeasSoftware;
+using BeatLyrics.Tool.DataProvider.OnlineLyric.Models;
+using BeatLyrics.Tool.Models;
 using Newtonsoft.Json;
+using System;
+using System.Linq;
 
 namespace BeatLyrics.Tool.DataProvider.OnlineLyric
 {
@@ -73,19 +73,46 @@ namespace BeatLyrics.Tool.DataProvider.OnlineLyric
                 }
             });
 
-            if (obj.lrc?.lyric != null)
+            if (string.IsNullOrWhiteSpace(obj.lrc?.lyric)) return false;
+
+            input.DetailsText = obj.lrc.lyric;
+            input.Details = ParseLrc(obj.lrc.lyric, input.Duration);
+
+            if (input.Details.Count == 0)
             {
-                input.DetailsText = obj.lrc.lyric;
-                input.Details = ParseLrc(obj.lrc.lyric, input.Duration);
+                var lines = obj.lrc.lyric.Split('\r', '\n');
+                var pos = 0;
+                var step = 1000;
+                var len = 500;
+
+                input.Details.Add(new LyricDetailExt
+                {
+                    Text = "** Warning: Only text found **",
+                    DurationMs = len,
+                    TimeMs = pos,
+                });
+
+                pos += step;
+
+                foreach (var line in lines)
+                {
+                    if (string.IsNullOrWhiteSpace(line)) continue;
+                    input.Details.Add(new LyricDetailExt
+                    {
+                        Text = line,
+                        DurationMs = len,
+                        TimeMs = pos,
+                    });
+                    pos += step;
+                }
             }
 
-            if (obj.tlyric?.lyric != null)
+            if (false == string.IsNullOrWhiteSpace(obj.tlyric?.lyric))
             {
                 input.DetailsTranslatedText = obj.tlyric.lyric;
                 input.DetailsTranslated = ParseLrc(obj.tlyric.lyric, input.Duration);
             }
 
-            if (obj.lrc == null) return false;
             input.IsDetailsLoaded = true;
             return true;
         }

+ 60 - 0
BeatLyrics.Tool/Dialogs/ContextDialog.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace BeatLyrics.Tool.Dialogs
+{
+    internal class ContextDialog
+    {
+        public static void PopTextBox(Point screenPoint, string orig, Action<DialogResult, string> ret)
+        {
+            var form = new Form
+            {
+                FormBorderStyle = FormBorderStyle.None,
+                StartPosition = FormStartPosition.Manual,
+                Location = screenPoint,
+                ShowInTaskbar = false,
+            };
+            var textBox = new TextBox
+            {
+                Left = 0,
+                Top = 0,
+            };
+
+            form.Controls.Add(textBox);
+
+            var closed = false;
+            textBox.PreviewKeyDown += delegate (object sender, PreviewKeyDownEventArgs args)
+            {
+                if (closed) return;
+                if (args.KeyCode == Keys.Escape)
+                {
+                    form.DialogResult = DialogResult.Cancel;
+                    form.Close();
+                }
+
+                if (args.KeyCode == Keys.Enter)
+                {
+                    form.DialogResult = DialogResult.OK;
+                    form.Close();
+                }
+            };
+            textBox.LostFocus += delegate
+            {
+                if (closed) return;
+                form.DialogResult = DialogResult.OK;
+                form.Close();
+            };
+
+            form.FormClosed += delegate { closed = true; ret(form.DialogResult, textBox.Text); };
+
+            form.Show();
+            form.Height = textBox.Height;
+            textBox.Text = orig;
+            textBox.SelectAll();
+            var tw = TextRenderer.MeasureText(orig, textBox.Font).Width;
+            if (tw > textBox.Width) textBox.Width = tw;
+            form.Width = textBox.Width;
+        }
+    }
+}

+ 26 - 25
BeatLyrics.Tool/Dialogs/OnlineLyricsDialog.Designer.cs

@@ -166,7 +166,7 @@
             tabPage2.Padding = new System.Windows.Forms.Padding(3);
             tabPage2.Size = new System.Drawing.Size(389, 94);
             tabPage2.TabIndex = 1;
-            tabPage2.Text = "Todo";
+            tabPage2.Text = "More(TODO)";
             tabPage2.UseVisualStyleBackColor = true;
             // 
             // label4
@@ -176,7 +176,8 @@
             label4.Name = "label4";
             label4.Size = new System.Drawing.Size(383, 88);
             label4.TabIndex = 0;
-            label4.Text = "More Online Lyrics";
+            label4.Text = "More Online Lyrics\r\ne.g. musixmatch\r\ne.g. lyrics.kugou.com\r\ne.g. c.y.qq.com\r\ne.g." +
+    " music.baidu.com\r\ne.g. sou.kuwo.cn\r\ne.g. www.xiami.com";
             label4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
             // 
             // panel1
@@ -309,6 +310,29 @@
             this.OnlineTabControl.Size = new System.Drawing.Size(397, 120);
             this.OnlineTabControl.TabIndex = 1;
             // 
+            // olvColumn2
+            // 
+            olvColumn2.AspectName = "Name";
+            olvColumn2.Text = "Name";
+            olvColumn2.Width = 240;
+            // 
+            // olvColumn1
+            // 
+            olvColumn1.AspectName = "DurationText";
+            olvColumn1.Text = "Duration";
+            // 
+            // olvColumn3
+            // 
+            olvColumn3.AspectName = "Artists";
+            olvColumn3.Text = "Artists";
+            olvColumn3.Width = 120;
+            // 
+            // olvColumn4
+            // 
+            olvColumn4.AspectName = "Remarks";
+            olvColumn4.Text = "Remarks";
+            olvColumn4.Width = 320;
+            // 
             // SearchResultTabPage
             // 
             this.SearchResultTabPage.Controls.Add(this.ResultObjectListView);
@@ -345,29 +369,6 @@
             this.ResultObjectListView.View = System.Windows.Forms.View.Details;
             this.ResultObjectListView.DoubleClick += new System.EventHandler(this.ResultObjectListView_DoubleClick);
             // 
-            // olvColumn2
-            // 
-            olvColumn2.AspectName = "Name";
-            olvColumn2.Text = "Name";
-            olvColumn2.Width = 240;
-            // 
-            // olvColumn1
-            // 
-            olvColumn1.AspectName = "DurationText";
-            olvColumn1.Text = "Duration";
-            // 
-            // olvColumn3
-            // 
-            olvColumn3.AspectName = "Artists";
-            olvColumn3.Text = "Artists";
-            olvColumn3.Width = 120;
-            // 
-            // olvColumn4
-            // 
-            olvColumn4.AspectName = "Remarks";
-            olvColumn4.Text = "Remarks";
-            olvColumn4.Width = 320;
-            // 
             // SearchResultTabControl
             // 
             this.SearchResultTabControl.Controls.Add(this.SearchResultTabPage);

+ 178 - 0
BeatLyrics.Tool/Dialogs/PlotDialog.Designer.cs

@@ -0,0 +1,178 @@
+namespace BeatLyrics.Tool.Dialogs
+{
+    partial class PlotDialog
+    {
+        /// <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.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
+            System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
+            System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
+            this.MainChart = new System.Windows.Forms.DataVisualization.Charting.Chart();
+            this.SelectTrackBar = new System.Windows.Forms.TrackBar();
+            this.MinFreqTrackBar = new System.Windows.Forms.TrackBar();
+            this.MaxFreqTrackBar = new System.Windows.Forms.TrackBar();
+            this.MaxValueTrackBar = new System.Windows.Forms.TrackBar();
+            this.MinValueTrackBar = new System.Windows.Forms.TrackBar();
+            this.InfoLabel = new System.Windows.Forms.Label();
+            ((System.ComponentModel.ISupportInitialize)(this.MainChart)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.SelectTrackBar)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.MinFreqTrackBar)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.MaxFreqTrackBar)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.MaxValueTrackBar)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.MinValueTrackBar)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // MainChart
+            // 
+            chartArea1.Name = "ChartArea1";
+            this.MainChart.ChartAreas.Add(chartArea1);
+            this.MainChart.Dock = System.Windows.Forms.DockStyle.Fill;
+            legend1.Name = "Legend1";
+            this.MainChart.Legends.Add(legend1);
+            this.MainChart.Location = new System.Drawing.Point(90, 70);
+            this.MainChart.Name = "MainChart";
+            series1.ChartArea = "ChartArea1";
+            series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
+            series1.IsVisibleInLegend = false;
+            series1.Legend = "Legend1";
+            series1.Name = "Series1";
+            this.MainChart.Series.Add(series1);
+            this.MainChart.Size = new System.Drawing.Size(785, 480);
+            this.MainChart.TabIndex = 0;
+            this.MainChart.Text = "chart1";
+            this.MainChart.Click += new System.EventHandler(this.MainChart_Click);
+            // 
+            // SelectTrackBar
+            // 
+            this.SelectTrackBar.Dock = System.Windows.Forms.DockStyle.Top;
+            this.SelectTrackBar.Location = new System.Drawing.Point(0, 25);
+            this.SelectTrackBar.Name = "SelectTrackBar";
+            this.SelectTrackBar.Size = new System.Drawing.Size(875, 45);
+            this.SelectTrackBar.TabIndex = 1;
+            this.SelectTrackBar.TickFrequency = 100;
+            this.SelectTrackBar.TickStyle = System.Windows.Forms.TickStyle.Both;
+            this.SelectTrackBar.Scroll += new System.EventHandler(this.SelectTrackBar_Scroll);
+            this.SelectTrackBar.MouseDown += new System.Windows.Forms.MouseEventHandler(this.SelectTrackBar_MouseDown);
+            this.SelectTrackBar.MouseUp += new System.Windows.Forms.MouseEventHandler(this.SelectTrackBar_MouseUp);
+            // 
+            // MinFreqTrackBar
+            // 
+            this.MinFreqTrackBar.Dock = System.Windows.Forms.DockStyle.Bottom;
+            this.MinFreqTrackBar.Location = new System.Drawing.Point(90, 550);
+            this.MinFreqTrackBar.Maximum = 10000;
+            this.MinFreqTrackBar.Name = "MinFreqTrackBar";
+            this.MinFreqTrackBar.Size = new System.Drawing.Size(785, 45);
+            this.MinFreqTrackBar.TabIndex = 2;
+            this.MinFreqTrackBar.TickFrequency = 100;
+            this.MinFreqTrackBar.TickStyle = System.Windows.Forms.TickStyle.Both;
+            this.MinFreqTrackBar.Scroll += new System.EventHandler(this.MinTrackBar_Scroll);
+            // 
+            // MaxFreqTrackBar
+            // 
+            this.MaxFreqTrackBar.Dock = System.Windows.Forms.DockStyle.Bottom;
+            this.MaxFreqTrackBar.Location = new System.Drawing.Point(90, 595);
+            this.MaxFreqTrackBar.Maximum = 10000;
+            this.MaxFreqTrackBar.Name = "MaxFreqTrackBar";
+            this.MaxFreqTrackBar.Size = new System.Drawing.Size(785, 45);
+            this.MaxFreqTrackBar.TabIndex = 3;
+            this.MaxFreqTrackBar.TickFrequency = 100;
+            this.MaxFreqTrackBar.TickStyle = System.Windows.Forms.TickStyle.Both;
+            this.MaxFreqTrackBar.Value = 10000;
+            this.MaxFreqTrackBar.Scroll += new System.EventHandler(this.MaxTrackBar_Scroll);
+            // 
+            // MaxValueTrackBar
+            // 
+            this.MaxValueTrackBar.Dock = System.Windows.Forms.DockStyle.Left;
+            this.MaxValueTrackBar.Location = new System.Drawing.Point(45, 70);
+            this.MaxValueTrackBar.Maximum = 10000;
+            this.MaxValueTrackBar.Name = "MaxValueTrackBar";
+            this.MaxValueTrackBar.Orientation = System.Windows.Forms.Orientation.Vertical;
+            this.MaxValueTrackBar.Size = new System.Drawing.Size(45, 570);
+            this.MaxValueTrackBar.TabIndex = 4;
+            this.MaxValueTrackBar.TickFrequency = 100;
+            this.MaxValueTrackBar.TickStyle = System.Windows.Forms.TickStyle.Both;
+            this.MaxValueTrackBar.Value = 10000;
+            this.MaxValueTrackBar.Scroll += new System.EventHandler(this.MaxValueTrackBar_Scroll);
+            // 
+            // MinValueTrackBar
+            // 
+            this.MinValueTrackBar.Dock = System.Windows.Forms.DockStyle.Left;
+            this.MinValueTrackBar.Location = new System.Drawing.Point(0, 70);
+            this.MinValueTrackBar.Maximum = 10000;
+            this.MinValueTrackBar.Name = "MinValueTrackBar";
+            this.MinValueTrackBar.Orientation = System.Windows.Forms.Orientation.Vertical;
+            this.MinValueTrackBar.Size = new System.Drawing.Size(45, 570);
+            this.MinValueTrackBar.TabIndex = 5;
+            this.MinValueTrackBar.TickFrequency = 100;
+            this.MinValueTrackBar.TickStyle = System.Windows.Forms.TickStyle.Both;
+            this.MinValueTrackBar.Scroll += new System.EventHandler(this.MinValueTrackBar_Scroll);
+            // 
+            // InfoLabel
+            // 
+            this.InfoLabel.Dock = System.Windows.Forms.DockStyle.Top;
+            this.InfoLabel.Location = new System.Drawing.Point(0, 0);
+            this.InfoLabel.Name = "InfoLabel";
+            this.InfoLabel.Size = new System.Drawing.Size(875, 25);
+            this.InfoLabel.TabIndex = 6;
+            this.InfoLabel.Text = "label1";
+            // 
+            // PlotDialog
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(875, 640);
+            this.Controls.Add(this.MainChart);
+            this.Controls.Add(this.MinFreqTrackBar);
+            this.Controls.Add(this.MaxFreqTrackBar);
+            this.Controls.Add(this.MaxValueTrackBar);
+            this.Controls.Add(this.MinValueTrackBar);
+            this.Controls.Add(this.SelectTrackBar);
+            this.Controls.Add(this.InfoLabel);
+            this.Name = "PlotDialog";
+            this.Text = "PlotDialog";
+            ((System.ComponentModel.ISupportInitialize)(this.MainChart)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.SelectTrackBar)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.MinFreqTrackBar)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.MaxFreqTrackBar)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.MaxValueTrackBar)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.MinValueTrackBar)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.DataVisualization.Charting.Chart MainChart;
+        private System.Windows.Forms.TrackBar SelectTrackBar;
+        private System.Windows.Forms.TrackBar MinFreqTrackBar;
+        private System.Windows.Forms.TrackBar MaxFreqTrackBar;
+        private System.Windows.Forms.TrackBar MaxValueTrackBar;
+        private System.Windows.Forms.TrackBar MinValueTrackBar;
+        private System.Windows.Forms.Label InfoLabel;
+    }
+}

+ 80 - 0
BeatLyrics.Tool/Dialogs/PlotDialog.cs

@@ -0,0 +1,80 @@
+using BeatLyrics.Tool.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+using System.Windows.Forms.DataVisualization.Charting;
+
+namespace BeatLyrics.Tool.Dialogs
+{
+    internal partial class PlotDialog : Form
+    {
+        private readonly SpectrumAnalyzer.SaResult[] _items;
+        private readonly int _blockInMs;
+
+        public PlotDialog(SpectrumAnalyzer.SaResult[] items, int blockInMs)
+        {
+            _items = items;
+            _blockInMs = blockInMs;
+            InitializeComponent();
+            SelectTrackBar.Maximum = items.Length - 1;
+            UpdateChart();
+        }
+
+        private void UpdateChart()
+        {
+            MainChart.Series["Series1"].Points.Clear();
+            var item = _items[SelectTrackBar.Value];
+
+            var maxFreq = item.FreqAxis.Max();
+            var rngFreqMax = maxFreq * (MaxFreqTrackBar.Value / 10000.0);
+            var rngFreqMin = maxFreq * (MinFreqTrackBar.Value / 10000.0);
+
+            var minValue = item.Values.Min();
+            var rngValue = item.Values.Max() - minValue;
+
+            var rngValueMin = minValue + rngValue * (MinValueTrackBar.Value / 10000.0);
+            var rngValueMax = minValue + rngValue * (MaxValueTrackBar.Value / 10000.0);
+
+            var axis = new List<double>();
+            var values = new List<double>();
+
+            for (var i = 0; i < item.FreqAxis.Length; i++)
+            {
+                if (item.FreqAxis[i] >= rngFreqMin && item.FreqAxis[i] <= rngFreqMax
+                 && item.Values[i] >= rngValueMin && item.Values[i] <= rngValueMax)
+                {
+                    axis.Add(item.FreqAxis[i]);
+                    values.Add(item.Values[i]);
+                }
+            }
+
+            InfoLabel.Text = $"Pos:{TimeSpan.FromMilliseconds(SelectTrackBar.Value * _blockInMs)}";
+
+            MainChart.Series["Series1"].Points.DataBindXY(axis, values);
+        }
+
+        private void SelectTrackBar_Scroll(object sender, EventArgs e)
+        {
+            UpdateChart();
+            OggAudioPlayer.Position = TimeSpan.FromMilliseconds(_blockInMs * SelectTrackBar.Value);
+        }
+
+        private void MaxTrackBar_Scroll(object sender, EventArgs e) => UpdateChart();
+
+        private void MinTrackBar_Scroll(object sender, EventArgs e) => UpdateChart();
+
+        private void MinValueTrackBar_Scroll(object sender, EventArgs e) => UpdateChart();
+
+        private void MaxValueTrackBar_Scroll(object sender, EventArgs e) => UpdateChart();
+
+        private void SelectTrackBar_MouseDown(object sender, MouseEventArgs e) => OggAudioPlayer.Play();
+
+        private void SelectTrackBar_MouseUp(object sender, MouseEventArgs e) => OggAudioPlayer.Stop();
+
+        private void MainChart_Click(object sender, EventArgs e) =>
+            MainChart.Series["Series1"].ChartType = MainChart.Series["Series1"].ChartType == SeriesChartType.Line
+                ? SeriesChartType.Column
+                : SeriesChartType.Line;
+    }
+}

+ 120 - 0
BeatLyrics.Tool/Dialogs/PlotDialog.resx

@@ -0,0 +1,120 @@
+<?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>
+</root>

+ 171 - 16
BeatLyrics.Tool/Dialogs/TimelineEditDialog.Designer.cs

@@ -42,8 +42,20 @@ namespace BeatLyrics.Tool.Dialogs
             this.ScaleTrackBar = new System.Windows.Forms.TrackBar();
             this.ScaleLabel = new System.Windows.Forms.Label();
             this.SaveAndCloseButton = new System.Windows.Forms.Button();
+            this.splitContainer1 = new System.Windows.Forms.SplitContainer();
+            this.FreqRangeBar = new BeatLyrics.Tool.UserControls.ZzzzRangeBar();
+            this.ValueRangeBar = new BeatLyrics.Tool.UserControls.ZzzzRangeBar();
+            this.ViewSpectrumButton = new System.Windows.Forms.Button();
+            this.ReSpectrumButton = new System.Windows.Forms.Button();
+            this.SaMsUpDown = new System.Windows.Forms.NumericUpDown();
+            this.SaWindoeTypeComboBox = new System.Windows.Forms.ComboBox();
             ((System.ComponentModel.ISupportInitialize)(this.PlayPosTrackBar)).BeginInit();
             ((System.ComponentModel.ISupportInitialize)(this.ScaleTrackBar)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
+            this.splitContainer1.Panel1.SuspendLayout();
+            this.splitContainer1.Panel2.SuspendLayout();
+            this.splitContainer1.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.SaMsUpDown)).BeginInit();
             this.SuspendLayout();
             // 
             // PlayPosTrackBar
@@ -53,7 +65,7 @@ namespace BeatLyrics.Tool.Dialogs
             this.PlayPosTrackBar.Location = new System.Drawing.Point(12, 12);
             this.PlayPosTrackBar.Maximum = 300000;
             this.PlayPosTrackBar.Name = "PlayPosTrackBar";
-            this.PlayPosTrackBar.Size = new System.Drawing.Size(943, 45);
+            this.PlayPosTrackBar.Size = new System.Drawing.Size(1264, 45);
             this.PlayPosTrackBar.TabIndex = 1;
             this.PlayPosTrackBar.TickFrequency = 10000;
             this.PlayPosTrackBar.TickStyle = System.Windows.Forms.TickStyle.Both;
@@ -68,7 +80,7 @@ namespace BeatLyrics.Tool.Dialogs
             | System.Windows.Forms.AnchorStyles.Right)));
             this.TextLabel1.Location = new System.Drawing.Point(118, 66);
             this.TextLabel1.Name = "TextLabel1";
-            this.TextLabel1.Size = new System.Drawing.Size(690, 17);
+            this.TextLabel1.Size = new System.Drawing.Size(1011, 17);
             this.TextLabel1.TabIndex = 2;
             this.TextLabel1.Text = "此处应有歌词";
             this.TextLabel1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@@ -98,7 +110,7 @@ namespace BeatLyrics.Tool.Dialogs
             | System.Windows.Forms.AnchorStyles.Right)));
             this.TextLabel2.Location = new System.Drawing.Point(118, 88);
             this.TextLabel2.Name = "TextLabel2";
-            this.TextLabel2.Size = new System.Drawing.Size(690, 17);
+            this.TextLabel2.Size = new System.Drawing.Size(1011, 17);
             this.TextLabel2.TabIndex = 2;
             this.TextLabel2.Text = "此处应有翻译";
             this.TextLabel2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@@ -110,14 +122,17 @@ namespace BeatLyrics.Tool.Dialogs
             // 
             // EditorControl2
             // 
-            this.EditorControl2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
-            | System.Windows.Forms.AnchorStyles.Right)));
             this.EditorControl2.DisplayScale = 0.1F;
-            this.EditorControl2.Location = new System.Drawing.Point(12, 238);
+            this.EditorControl2.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.EditorControl2.Location = new System.Drawing.Point(0, 0);
             this.EditorControl2.LockDown = false;
+            this.EditorControl2.MaxSaFreq = 0D;
+            this.EditorControl2.MaxSaValue = 0D;
+            this.EditorControl2.MinSaFreq = 0D;
+            this.EditorControl2.MinSaValue = 0D;
             this.EditorControl2.Name = "EditorControl2";
             this.EditorControl2.PlayPos = 0;
-            this.EditorControl2.Size = new System.Drawing.Size(943, 113);
+            this.EditorControl2.Size = new System.Drawing.Size(1117, 230);
             this.EditorControl2.TabIndex = 0;
             this.EditorControl2.PosChangeBegin += new System.EventHandler(this.EditorControl_PosChangeBegin);
             this.EditorControl2.PosChange += new System.EventHandler<int>(this.EditorControl_PosChange);
@@ -125,14 +140,17 @@ namespace BeatLyrics.Tool.Dialogs
             // 
             // EditorControl1
             // 
-            this.EditorControl1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
-            | System.Windows.Forms.AnchorStyles.Right)));
             this.EditorControl1.DisplayScale = 0.1F;
-            this.EditorControl1.Location = new System.Drawing.Point(12, 131);
+            this.EditorControl1.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.EditorControl1.Location = new System.Drawing.Point(0, 0);
             this.EditorControl1.LockDown = false;
+            this.EditorControl1.MaxSaFreq = 0D;
+            this.EditorControl1.MaxSaValue = 0D;
+            this.EditorControl1.MinSaFreq = 0D;
+            this.EditorControl1.MinSaValue = 0D;
             this.EditorControl1.Name = "EditorControl1";
             this.EditorControl1.PlayPos = 0;
-            this.EditorControl1.Size = new System.Drawing.Size(943, 101);
+            this.EditorControl1.Size = new System.Drawing.Size(1117, 229);
             this.EditorControl1.TabIndex = 0;
             this.EditorControl1.PosChangeBegin += new System.EventHandler(this.EditorControl_PosChangeBegin);
             this.EditorControl1.PosChange += new System.EventHandler<int>(this.EditorControl_PosChange);
@@ -141,7 +159,7 @@ namespace BeatLyrics.Tool.Dialogs
             // ScaleTrackBar
             // 
             this.ScaleTrackBar.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.ScaleTrackBar.Location = new System.Drawing.Point(814, 80);
+            this.ScaleTrackBar.Location = new System.Drawing.Point(1135, 80);
             this.ScaleTrackBar.Maximum = 1000;
             this.ScaleTrackBar.Minimum = 1;
             this.ScaleTrackBar.Name = "ScaleTrackBar";
@@ -155,7 +173,7 @@ namespace BeatLyrics.Tool.Dialogs
             // 
             this.ScaleLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
             this.ScaleLabel.AutoSize = true;
-            this.ScaleLabel.Location = new System.Drawing.Point(812, 65);
+            this.ScaleLabel.Location = new System.Drawing.Point(1133, 65);
             this.ScaleLabel.Name = "ScaleLabel";
             this.ScaleLabel.Size = new System.Drawing.Size(23, 12);
             this.ScaleLabel.TabIndex = 2;
@@ -171,11 +189,138 @@ namespace BeatLyrics.Tool.Dialogs
             this.SaveAndCloseButton.UseVisualStyleBackColor = true;
             this.SaveAndCloseButton.Click += new System.EventHandler(this.SaveAndCloseButton_Click);
             // 
+            // splitContainer1
+            // 
+            this.splitContainer1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.splitContainer1.Location = new System.Drawing.Point(12, 131);
+            this.splitContainer1.Name = "splitContainer1";
+            this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal;
+            // 
+            // splitContainer1.Panel1
+            // 
+            this.splitContainer1.Panel1.Controls.Add(this.EditorControl1);
+            // 
+            // splitContainer1.Panel2
+            // 
+            this.splitContainer1.Panel2.Controls.Add(this.EditorControl2);
+            this.splitContainer1.Size = new System.Drawing.Size(1117, 463);
+            this.splitContainer1.SplitterDistance = 229;
+            this.splitContainer1.TabIndex = 6;
+            // 
+            // FreqRangeBar
+            // 
+            this.FreqRangeBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.FreqRangeBar.DivisionNum = 10;
+            this.FreqRangeBar.HeightOfBar = 8;
+            this.FreqRangeBar.HeightOfMark = 24;
+            this.FreqRangeBar.HeightOfTick = 6;
+            this.FreqRangeBar.InnerColor = System.Drawing.Color.LightGreen;
+            this.FreqRangeBar.Location = new System.Drawing.Point(1095, 220);
+            this.FreqRangeBar.Name = "FreqRangeBar";
+            this.FreqRangeBar.Orientation = BeatLyrics.Tool.UserControls.ZzzzRangeBar.RangeBarOrientation.vertical;
+            this.FreqRangeBar.RangeMaximum = 10;
+            this.FreqRangeBar.RangeMinimum = 10;
+            this.FreqRangeBar.ScaleOrientation = BeatLyrics.Tool.UserControls.ZzzzRangeBar.TopBottomOrientation.bottom;
+            this.FreqRangeBar.Size = new System.Drawing.Size(108, 374);
+            this.FreqRangeBar.TabIndex = 1;
+            this.FreqRangeBar.TotalMaximum = 2400000;
+            this.FreqRangeBar.TotalMinimum = 1;
+            this.FreqRangeBar.RangeChanging += new BeatLyrics.Tool.UserControls.ZzzzRangeBar.RangeChangedEventHandler(this.RangeBar_RangeChanging);
+            // 
+            // ValueRangeBar
+            // 
+            this.ValueRangeBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.ValueRangeBar.DivisionNum = 10;
+            this.ValueRangeBar.HeightOfBar = 8;
+            this.ValueRangeBar.HeightOfMark = 24;
+            this.ValueRangeBar.HeightOfTick = 6;
+            this.ValueRangeBar.InnerColor = System.Drawing.Color.LightGreen;
+            this.ValueRangeBar.Location = new System.Drawing.Point(1178, 220);
+            this.ValueRangeBar.Name = "ValueRangeBar";
+            this.ValueRangeBar.Orientation = BeatLyrics.Tool.UserControls.ZzzzRangeBar.RangeBarOrientation.vertical;
+            this.ValueRangeBar.RangeMaximum = 0;
+            this.ValueRangeBar.RangeMinimum = 0;
+            this.ValueRangeBar.ScaleOrientation = BeatLyrics.Tool.UserControls.ZzzzRangeBar.TopBottomOrientation.bottom;
+            this.ValueRangeBar.Size = new System.Drawing.Size(98, 374);
+            this.ValueRangeBar.TabIndex = 1;
+            this.ValueRangeBar.TotalMaximum = 0;
+            this.ValueRangeBar.TotalMinimum = -200;
+            this.ValueRangeBar.RangeChanging += new BeatLyrics.Tool.UserControls.ZzzzRangeBar.RangeChangedEventHandler(this.RangeBar_RangeChanging);
+            // 
+            // ViewSpectrumButton
+            // 
+            this.ViewSpectrumButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.ViewSpectrumButton.Enabled = false;
+            this.ViewSpectrumButton.Location = new System.Drawing.Point(1135, 191);
+            this.ViewSpectrumButton.Name = "ViewSpectrumButton";
+            this.ViewSpectrumButton.Size = new System.Drawing.Size(141, 23);
+            this.ViewSpectrumButton.TabIndex = 7;
+            this.ViewSpectrumButton.Text = "View Spectrum";
+            this.ViewSpectrumButton.UseVisualStyleBackColor = true;
+            this.ViewSpectrumButton.Click += new System.EventHandler(this.ViewSpectrumButton_Click);
+            // 
+            // ReSpectrumButton
+            // 
+            this.ReSpectrumButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.ReSpectrumButton.Enabled = false;
+            this.ReSpectrumButton.Location = new System.Drawing.Point(1135, 162);
+            this.ReSpectrumButton.Name = "ReSpectrumButton";
+            this.ReSpectrumButton.Size = new System.Drawing.Size(141, 23);
+            this.ReSpectrumButton.TabIndex = 7;
+            this.ReSpectrumButton.Text = "Re Spectrum (MS)";
+            this.ReSpectrumButton.UseVisualStyleBackColor = true;
+            this.ReSpectrumButton.Click += new System.EventHandler(this.ReSpectrumButton_Click);
+            // 
+            // SaMsUpDown
+            // 
+            this.SaMsUpDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.SaMsUpDown.Location = new System.Drawing.Point(1135, 131);
+            this.SaMsUpDown.Maximum = new decimal(new int[] {
+            1000,
+            0,
+            0,
+            0});
+            this.SaMsUpDown.Minimum = new decimal(new int[] {
+            10,
+            0,
+            0,
+            0});
+            this.SaMsUpDown.Name = "SaMsUpDown";
+            this.SaMsUpDown.Size = new System.Drawing.Size(56, 21);
+            this.SaMsUpDown.TabIndex = 8;
+            this.SaMsUpDown.Value = new decimal(new int[] {
+            50,
+            0,
+            0,
+            0});
+            // 
+            // SaWindoeTypeComboBox
+            // 
+            this.SaWindoeTypeComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.SaWindoeTypeComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.SaWindoeTypeComboBox.FormattingEnabled = true;
+            this.SaWindoeTypeComboBox.Location = new System.Drawing.Point(1197, 130);
+            this.SaWindoeTypeComboBox.Name = "SaWindoeTypeComboBox";
+            this.SaWindoeTypeComboBox.Size = new System.Drawing.Size(79, 20);
+            this.SaWindoeTypeComboBox.TabIndex = 9;
+            this.SaWindoeTypeComboBox.SelectedValueChanged += new System.EventHandler(this.SaWindoeTypeComboBox_SelectedValueChanged);
+            // 
             // TimelineEditForm
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
-            this.ClientSize = new System.Drawing.Size(967, 363);
+            this.ClientSize = new System.Drawing.Size(1288, 606);
+            this.Controls.Add(this.SaWindoeTypeComboBox);
+            this.Controls.Add(this.SaMsUpDown);
+            this.Controls.Add(this.splitContainer1);
+            this.Controls.Add(this.FreqRangeBar);
+            this.Controls.Add(this.ReSpectrumButton);
+            this.Controls.Add(this.ViewSpectrumButton);
+            this.Controls.Add(this.ValueRangeBar);
             this.Controls.Add(this.SaveAndCloseButton);
             this.Controls.Add(this.ScaleTrackBar);
             this.Controls.Add(this.PlayButton);
@@ -184,8 +329,6 @@ namespace BeatLyrics.Tool.Dialogs
             this.Controls.Add(this.TextLabel2);
             this.Controls.Add(this.TextLabel1);
             this.Controls.Add(this.PlayPosTrackBar);
-            this.Controls.Add(this.EditorControl2);
-            this.Controls.Add(this.EditorControl1);
             this.KeyPreview = true;
             this.MinimizeBox = false;
             this.Name = "TimelineEditForm";
@@ -196,6 +339,11 @@ namespace BeatLyrics.Tool.Dialogs
             this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.EditForm_KeyDown);
             ((System.ComponentModel.ISupportInitialize)(this.PlayPosTrackBar)).EndInit();
             ((System.ComponentModel.ISupportInitialize)(this.ScaleTrackBar)).EndInit();
+            this.splitContainer1.Panel1.ResumeLayout(false);
+            this.splitContainer1.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
+            this.splitContainer1.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.SaMsUpDown)).EndInit();
             this.ResumeLayout(false);
             this.PerformLayout();
 
@@ -214,6 +362,13 @@ namespace BeatLyrics.Tool.Dialogs
         private System.Windows.Forms.TrackBar ScaleTrackBar;
         private System.Windows.Forms.Label ScaleLabel;
         private System.Windows.Forms.Button SaveAndCloseButton;
+        private System.Windows.Forms.SplitContainer splitContainer1;
+        private ZzzzRangeBar FreqRangeBar;
+        private ZzzzRangeBar ValueRangeBar;
+        private System.Windows.Forms.Button ViewSpectrumButton;
+        private System.Windows.Forms.Button ReSpectrumButton;
+        private System.Windows.Forms.NumericUpDown SaMsUpDown;
+        private System.Windows.Forms.ComboBox SaWindoeTypeComboBox;
     }
 }
 

+ 95 - 9
BeatLyrics.Tool/Dialogs/TimelineEditDialog.cs

@@ -6,16 +6,23 @@ using Newtonsoft.Json;
 using System;
 using System.IO;
 using System.Linq;
+using System.Threading.Tasks;
 using System.Windows.Forms;
+using BeatLyrics.Tool.RefLibs;
 
 namespace BeatLyrics.Tool.Dialogs
 {
-    internal partial class TimelineEditForm : Form
+    internal partial class TimelineEditForm : BaseForm
     {
+        //TODO: range select control for Spectrum visualize
+
         private readonly LevelInfo _levelInfo;
         private readonly string _filename;
 
         private string _filePath;
+        private int _blockInMs;
+        private SpectrumAnalyzer.SaResult[] _sa;
+        private DSP.Window.Type _windowType;
 
         public TimelineEditForm(LevelInfo levelInfo, string filename)
         {
@@ -35,13 +42,52 @@ namespace BeatLyrics.Tool.Dialogs
 
             EditorControl1.Items = lli.Main.ToList();
             if (lli.Subtitle != null) EditorControl2.Items = lli.Subtitle.ToList();
+
+            SaWindoeTypeComboBox.DataSource = Enum.GetValues(typeof(RefLibs.DSP.Window.Type));
+            SaWindoeTypeComboBox.SelectedItem = RefLibs.DSP.Window.Type.Rectangular;
+
+            Task.Factory.StartNew(() =>
+            {
+                _blockInMs = (int)SaMsUpDown.Value;
+
+                _sa = SpectrumAnalyzer.RunOgg(_levelInfo.MediaPath, _blockInMs, _windowType);
+
+                RunOnUiThread(UpdateSaResult);
+                RunOnUiThread(delegate
+                {
+                    FreqRangeBar.RangeMinimum = 30000;
+                    FreqRangeBar.RangeMaximum = 200000;
+                    ValueRangeBar.RangeMinimum = -4000;
+                    ValueRangeBar.RangeMaximum = 0;
+                    RangeBar_RangeChanging(null, null);
+                });
+            });
+        }
+
+        private void UpdateSaResult()
+        {
+            ViewSpectrumButton.Enabled = true;
+
+            FreqRangeBar.TotalMaximum = (int)(_sa.Max(p => p.FreqAxis.Max()) * 100);
+            FreqRangeBar.TotalMinimum = (int)(_sa.Min(p => p.FreqAxis.Min()) * 100);
+            ValueRangeBar.TotalMaximum = (int)(_sa.Max(p => p.Values.Max()) * 100);
+            ValueRangeBar.TotalMinimum = -20000; // ValueRangeBar.TotalMinimum = (int)(_sa.Min(p => p.Values.Min()) * 100);
+
+            EditorControl1.SetSpectrumData(_sa, _blockInMs);
+            EditorControl2.SetSpectrumData(_sa, _blockInMs);
+
+            ReSpectrumButton.Enabled = true;
         }
 
         private void UpdatePos()
         {
             var timeSpan = OggAudioPlayer.Position;
 
-            PlayPosTrackBar.Value = (int)timeSpan.TotalMilliseconds;
+            var ms = (int)timeSpan.TotalMilliseconds;
+            PlayPosTrackBar.Value = ms > PlayPosTrackBar.Maximum
+                ? PlayPosTrackBar.Value = PlayPosTrackBar.Maximum
+                : PlayPosTrackBar.Value = ms;
+
             EditorControl1.PlayPos = PlayPosTrackBar.Value;
             EditorControl2.PlayPos = PlayPosTrackBar.Value;
 
@@ -51,13 +97,6 @@ namespace BeatLyrics.Tool.Dialogs
             TextLabel2.Text = EditorControl2.GetPlayText();
         }
 
-        private void PlayPosTrackBar_Scroll(object sender, EventArgs e)
-        {
-            SetPosSafe(PlayPosTrackBar.Value);
-
-            UpdatePos();
-        }
-
         private void SetPosSafe(int ms)
         {
             var position = TimeSpan.FromMilliseconds(ms);
@@ -66,6 +105,13 @@ namespace BeatLyrics.Tool.Dialogs
             OggAudioPlayer.Position = position;
         }
 
+        private void PlayPosTrackBar_Scroll(object sender, EventArgs e)
+        {
+            SetPosSafe(PlayPosTrackBar.Value);
+
+            UpdatePos();
+        }
+
         private void PlayButton_Click(object sender, EventArgs e)
         {
             if (!OggAudioPlayer.IsPlaying)
@@ -161,5 +207,45 @@ namespace BeatLyrics.Tool.Dialogs
                 PlayPosTrackBar.Tag = null;
             }
         }
+
+        private void ReSpectrumButton_Click(object sender, EventArgs e)
+        {
+            ReSpectrumButton.Enabled = false;
+
+            Task.Factory.StartNew(() =>
+            {
+                _blockInMs = (int)SaMsUpDown.Value;
+
+                _sa = SpectrumAnalyzer.RunOgg(_levelInfo.MediaPath, _blockInMs, _windowType);
+
+                RunOnUiThread(UpdateSaResult);
+            });
+        }
+
+        private void ViewSpectrumButton_Click(object sender, EventArgs e)
+        {
+            new PlotDialog(_sa, _blockInMs).Show();
+        }
+
+        private void RangeBar_RangeChanging(object sender, EventArgs e)
+        {
+            EditorControl1.MinSaFreq = FreqRangeBar.RangeMinimum / 100.0;
+            EditorControl1.MaxSaFreq = FreqRangeBar.RangeMaximum / 100.0;
+            EditorControl1.MinSaValue = ValueRangeBar.RangeMinimum / 100.0;
+            EditorControl1.MaxSaValue = ValueRangeBar.RangeMaximum / 100.0;
+
+            EditorControl2.MinSaFreq = FreqRangeBar.RangeMinimum / 100.0;
+            EditorControl2.MaxSaFreq = FreqRangeBar.RangeMaximum / 100.0;
+            EditorControl2.MinSaValue = ValueRangeBar.RangeMinimum / 100.0;
+            EditorControl2.MaxSaValue = ValueRangeBar.RangeMaximum / 100.0;
+
+            EditorControl1.Invalidate();
+            EditorControl2.Invalidate();
+        }
+
+        private void SaWindoeTypeComboBox_SelectedValueChanged(object sender, EventArgs e)
+        {
+            _windowType = (DSP.Window.Type)SaWindoeTypeComboBox.SelectedValue;
+        }
     }
 }

+ 117 - 48
BeatLyrics.Tool/MainForm.Designer.cs

@@ -56,11 +56,16 @@
             this.CoverPictureBox = new System.Windows.Forms.PictureBox();
             this.MainProgressBar = new System.Windows.Forms.ProgressBar();
             this.panel1 = new System.Windows.Forms.Panel();
-            this.SearchVerifyCheckBox = new System.Windows.Forms.CheckBox();
-            this.OtherRadioButton = new System.Windows.Forms.RadioButton();
-            this.SetOnlyRadioButton = new System.Windows.Forms.RadioButton();
+            this.panel3 = new System.Windows.Forms.Panel();
+            this.All2RadioButton = new System.Windows.Forms.RadioButton();
+            this.VerifyRadioButton = new System.Windows.Forms.RadioButton();
+            this.Others2RadioButton = new System.Windows.Forms.RadioButton();
+            this.panel2 = new System.Windows.Forms.Panel();
             this.AllRadioButton = new System.Windows.Forms.RadioButton();
+            this.SetOnlyRadioButton = new System.Windows.Forms.RadioButton();
+            this.OtherRadioButton = new System.Windows.Forms.RadioButton();
             this.SearchTextBox = new System.Windows.Forms.TextBox();
+            this.DirTimeLabel = new System.Windows.Forms.Label();
             label1 = new System.Windows.Forms.Label();
             label2 = new System.Windows.Forms.Label();
             label3 = new System.Windows.Forms.Label();
@@ -75,6 +80,8 @@
             ((System.ComponentModel.ISupportInitialize)(this.LocalLyricObjectListView)).BeginInit();
             ((System.ComponentModel.ISupportInitialize)(this.CoverPictureBox)).BeginInit();
             this.panel1.SuspendLayout();
+            this.panel3.SuspendLayout();
+            this.panel2.SuspendLayout();
             this.SuspendLayout();
             // 
             // label1
@@ -136,7 +143,7 @@
             this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
             this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
             this.splitContainer1.IsSplitterFixed = true;
-            this.splitContainer1.Location = new System.Drawing.Point(0, 44);
+            this.splitContainer1.Location = new System.Drawing.Point(0, 48);
             this.splitContainer1.Name = "splitContainer1";
             // 
             // splitContainer1.Panel1
@@ -161,12 +168,13 @@
             this.splitContainer1.Panel2.Controls.Add(this.SongAuthorTextBox);
             this.splitContainer1.Panel2.Controls.Add(label2);
             this.splitContainer1.Panel2.Controls.Add(this.SubNameTextBox);
+            this.splitContainer1.Panel2.Controls.Add(this.DirTimeLabel);
             this.splitContainer1.Panel2.Controls.Add(label1);
             this.splitContainer1.Panel2.Controls.Add(this.SongNameTextBox);
             this.splitContainer1.Panel2.Controls.Add(this.DirTextBox);
             this.splitContainer1.Panel2.Controls.Add(this.CoverPictureBox);
             this.splitContainer1.Panel2.Padding = new System.Windows.Forms.Padding(3);
-            this.splitContainer1.Size = new System.Drawing.Size(1082, 685);
+            this.splitContainer1.Size = new System.Drawing.Size(1082, 681);
             this.splitContainer1.SplitterDistance = 805;
             this.splitContainer1.TabIndex = 2;
             // 
@@ -181,7 +189,7 @@
             this.MainObjectListView.MultiSelect = false;
             this.MainObjectListView.Name = "MainObjectListView";
             this.MainObjectListView.ShowGroups = false;
-            this.MainObjectListView.Size = new System.Drawing.Size(805, 685);
+            this.MainObjectListView.Size = new System.Drawing.Size(805, 681);
             this.MainObjectListView.SmallImageList = this.MainImageList;
             this.MainObjectListView.TabIndex = 2;
             this.MainObjectListView.TileSize = new System.Drawing.Size(192, 56);
@@ -223,7 +231,7 @@
             this.LocalLyricObjectListView.Location = new System.Drawing.Point(6, 537);
             this.LocalLyricObjectListView.Name = "LocalLyricObjectListView";
             this.LocalLyricObjectListView.ShowGroups = false;
-            this.LocalLyricObjectListView.Size = new System.Drawing.Size(259, 142);
+            this.LocalLyricObjectListView.Size = new System.Drawing.Size(259, 138);
             this.LocalLyricObjectListView.TabIndex = 7;
             this.LocalLyricObjectListView.UseCompatibleStateImageBehavior = false;
             this.LocalLyricObjectListView.View = System.Windows.Forms.View.Details;
@@ -364,45 +372,91 @@
             // 
             // panel1
             // 
-            this.panel1.Controls.Add(this.SearchVerifyCheckBox);
-            this.panel1.Controls.Add(this.OtherRadioButton);
-            this.panel1.Controls.Add(this.SetOnlyRadioButton);
-            this.panel1.Controls.Add(this.AllRadioButton);
+            this.panel1.Controls.Add(this.panel3);
+            this.panel1.Controls.Add(this.panel2);
             this.panel1.Controls.Add(this.SearchTextBox);
             this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
             this.panel1.Location = new System.Drawing.Point(0, 10);
             this.panel1.Name = "panel1";
-            this.panel1.Size = new System.Drawing.Size(1082, 34);
+            this.panel1.Size = new System.Drawing.Size(1082, 38);
             this.panel1.TabIndex = 4;
             // 
-            // SearchVerifyCheckBox
-            // 
-            this.SearchVerifyCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.SearchVerifyCheckBox.Location = new System.Drawing.Point(1010, 7);
-            this.SearchVerifyCheckBox.Name = "SearchVerifyCheckBox";
-            this.SearchVerifyCheckBox.Size = new System.Drawing.Size(60, 17);
-            this.SearchVerifyCheckBox.TabIndex = 9;
-            this.SearchVerifyCheckBox.Text = "Verify";
-            this.SearchVerifyCheckBox.UseVisualStyleBackColor = true;
-            this.SearchVerifyCheckBox.CheckedChanged += new System.EventHandler(this.SearchVerifyCheckBox_CheckedChanged);
+            // panel3
+            // 
+            this.panel3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.panel3.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+            this.panel3.Controls.Add(this.All2RadioButton);
+            this.panel3.Controls.Add(this.VerifyRadioButton);
+            this.panel3.Controls.Add(this.Others2RadioButton);
+            this.panel3.Location = new System.Drawing.Point(890, 6);
+            this.panel3.Name = "panel3";
+            this.panel3.Size = new System.Drawing.Size(189, 22);
+            this.panel3.TabIndex = 10;
+            // 
+            // All2RadioButton
+            // 
+            this.All2RadioButton.AutoSize = true;
+            this.All2RadioButton.Checked = true;
+            this.All2RadioButton.Location = new System.Drawing.Point(3, 3);
+            this.All2RadioButton.Name = "All2RadioButton";
+            this.All2RadioButton.Size = new System.Drawing.Size(41, 16);
+            this.All2RadioButton.TabIndex = 1;
+            this.All2RadioButton.TabStop = true;
+            this.All2RadioButton.Text = "All";
+            this.All2RadioButton.UseVisualStyleBackColor = true;
+            this.All2RadioButton.CheckedChanged += new System.EventHandler(this.AllRadioButton_CheckedChanged);
+            // 
+            // VerifyRadioButton
+            // 
+            this.VerifyRadioButton.AutoSize = true;
+            this.VerifyRadioButton.Location = new System.Drawing.Point(50, 3);
+            this.VerifyRadioButton.Name = "VerifyRadioButton";
+            this.VerifyRadioButton.Size = new System.Drawing.Size(59, 16);
+            this.VerifyRadioButton.TabIndex = 2;
+            this.VerifyRadioButton.Text = "Verify";
+            this.VerifyRadioButton.UseVisualStyleBackColor = true;
+            this.VerifyRadioButton.CheckedChanged += new System.EventHandler(this.AllRadioButton_CheckedChanged);
+            // 
+            // Others2RadioButton
+            // 
+            this.Others2RadioButton.AutoSize = true;
+            this.Others2RadioButton.Location = new System.Drawing.Point(121, 3);
+            this.Others2RadioButton.Name = "Others2RadioButton";
+            this.Others2RadioButton.Size = new System.Drawing.Size(59, 16);
+            this.Others2RadioButton.TabIndex = 2;
+            this.Others2RadioButton.Text = "Others";
+            this.Others2RadioButton.UseVisualStyleBackColor = true;
+            this.Others2RadioButton.CheckedChanged += new System.EventHandler(this.AllRadioButton_CheckedChanged);
+            // 
+            // panel2
+            // 
+            this.panel2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+            this.panel2.Controls.Add(this.AllRadioButton);
+            this.panel2.Controls.Add(this.SetOnlyRadioButton);
+            this.panel2.Controls.Add(this.OtherRadioButton);
+            this.panel2.Location = new System.Drawing.Point(695, 6);
+            this.panel2.Name = "panel2";
+            this.panel2.Size = new System.Drawing.Size(189, 22);
+            this.panel2.TabIndex = 10;
             // 
-            // OtherRadioButton
+            // AllRadioButton
             // 
-            this.OtherRadioButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.OtherRadioButton.AutoSize = true;
-            this.OtherRadioButton.Location = new System.Drawing.Point(929, 8);
-            this.OtherRadioButton.Name = "OtherRadioButton";
-            this.OtherRadioButton.Size = new System.Drawing.Size(59, 16);
-            this.OtherRadioButton.TabIndex = 2;
-            this.OtherRadioButton.Text = "Others";
-            this.OtherRadioButton.UseVisualStyleBackColor = true;
-            this.OtherRadioButton.CheckedChanged += new System.EventHandler(this.AllRadioButton_CheckedChanged);
+            this.AllRadioButton.AutoSize = true;
+            this.AllRadioButton.Checked = true;
+            this.AllRadioButton.Location = new System.Drawing.Point(3, 3);
+            this.AllRadioButton.Name = "AllRadioButton";
+            this.AllRadioButton.Size = new System.Drawing.Size(41, 16);
+            this.AllRadioButton.TabIndex = 1;
+            this.AllRadioButton.TabStop = true;
+            this.AllRadioButton.Text = "All";
+            this.AllRadioButton.UseVisualStyleBackColor = true;
+            this.AllRadioButton.CheckedChanged += new System.EventHandler(this.AllRadioButton_CheckedChanged);
             // 
             // SetOnlyRadioButton
             // 
-            this.SetOnlyRadioButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
             this.SetOnlyRadioButton.AutoSize = true;
-            this.SetOnlyRadioButton.Location = new System.Drawing.Point(858, 8);
+            this.SetOnlyRadioButton.Location = new System.Drawing.Point(50, 3);
             this.SetOnlyRadioButton.Name = "SetOnlyRadioButton";
             this.SetOnlyRadioButton.Size = new System.Drawing.Size(65, 16);
             this.SetOnlyRadioButton.TabIndex = 2;
@@ -410,19 +464,16 @@
             this.SetOnlyRadioButton.UseVisualStyleBackColor = true;
             this.SetOnlyRadioButton.CheckedChanged += new System.EventHandler(this.AllRadioButton_CheckedChanged);
             // 
-            // AllRadioButton
+            // OtherRadioButton
             // 
-            this.AllRadioButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.AllRadioButton.AutoSize = true;
-            this.AllRadioButton.Checked = true;
-            this.AllRadioButton.Location = new System.Drawing.Point(811, 8);
-            this.AllRadioButton.Name = "AllRadioButton";
-            this.AllRadioButton.Size = new System.Drawing.Size(41, 16);
-            this.AllRadioButton.TabIndex = 1;
-            this.AllRadioButton.TabStop = true;
-            this.AllRadioButton.Text = "All";
-            this.AllRadioButton.UseVisualStyleBackColor = true;
-            this.AllRadioButton.CheckedChanged += new System.EventHandler(this.AllRadioButton_CheckedChanged);
+            this.OtherRadioButton.AutoSize = true;
+            this.OtherRadioButton.Location = new System.Drawing.Point(121, 3);
+            this.OtherRadioButton.Name = "OtherRadioButton";
+            this.OtherRadioButton.Size = new System.Drawing.Size(59, 16);
+            this.OtherRadioButton.TabIndex = 2;
+            this.OtherRadioButton.Text = "Others";
+            this.OtherRadioButton.UseVisualStyleBackColor = true;
+            this.OtherRadioButton.CheckedChanged += new System.EventHandler(this.AllRadioButton_CheckedChanged);
             // 
             // SearchTextBox
             // 
@@ -430,10 +481,19 @@
             | System.Windows.Forms.AnchorStyles.Right)));
             this.SearchTextBox.Location = new System.Drawing.Point(3, 7);
             this.SearchTextBox.Name = "SearchTextBox";
-            this.SearchTextBox.Size = new System.Drawing.Size(802, 21);
+            this.SearchTextBox.Size = new System.Drawing.Size(686, 21);
             this.SearchTextBox.TabIndex = 0;
             this.SearchTextBox.TextChanged += new System.EventHandler(this.SearchTextBox_TextChanged);
             // 
+            // DirTimeLabel
+            // 
+            this.DirTimeLabel.AutoSize = true;
+            this.DirTimeLabel.Location = new System.Drawing.Point(35, 271);
+            this.DirTimeLabel.Name = "DirTimeLabel";
+            this.DirTimeLabel.Size = new System.Drawing.Size(119, 12);
+            this.DirTimeLabel.TabIndex = 3;
+            this.DirTimeLabel.Text = "0000-00-00 00:00:00";
+            // 
             // MainForm
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
@@ -457,6 +517,10 @@
             ((System.ComponentModel.ISupportInitialize)(this.CoverPictureBox)).EndInit();
             this.panel1.ResumeLayout(false);
             this.panel1.PerformLayout();
+            this.panel3.ResumeLayout(false);
+            this.panel3.PerformLayout();
+            this.panel2.ResumeLayout(false);
+            this.panel2.PerformLayout();
             this.ResumeLayout(false);
 
         }
@@ -487,6 +551,11 @@
         private System.Windows.Forms.RadioButton AllRadioButton;
         private System.Windows.Forms.LinkLabel HashExploreHereLinkLabel;
         private System.Windows.Forms.CheckBox VerifyCheckBox;
-        private System.Windows.Forms.CheckBox SearchVerifyCheckBox;
+        private System.Windows.Forms.Panel panel3;
+        private System.Windows.Forms.RadioButton All2RadioButton;
+        private System.Windows.Forms.RadioButton VerifyRadioButton;
+        private System.Windows.Forms.RadioButton Others2RadioButton;
+        private System.Windows.Forms.Panel panel2;
+        private System.Windows.Forms.Label DirTimeLabel;
     }
 }

+ 19 - 3
BeatLyrics.Tool/MainForm.cs

@@ -27,6 +27,12 @@ namespace BeatLyrics.Tool
         private void RefreshList()
         {
             if (_refreshTask != null) return;
+            MainObjectListView.Items.Clear();
+            MainImageList.Images.Clear();
+
+            if (null != _allLevelInfos)
+                foreach (var item in _allLevelInfos) item.CoverImage.Dispose();
+
             _refreshTask = Task.Factory.StartNew(() =>
             {
                 RunOnUiThread(() =>
@@ -40,7 +46,7 @@ namespace BeatLyrics.Tool
                 {
                     MainProgressBar.Maximum = l;
                     MainProgressBar.Value = i;
-                }));
+                })).OrderBy(p => p.SongName).ToArray();
 
                 RunOnUiThread(() => MainProgressBar.Value = 0);
 
@@ -69,10 +75,11 @@ namespace BeatLyrics.Tool
                     }
                 });
 
+                _allLevelInfos = levels;
+
                 RunOnUiThread(() =>
                 {
                     MainProgressBar.Visible = false;
-                    _allLevelInfos = levels;
                     Search();
                 });
 
@@ -85,6 +92,7 @@ namespace BeatLyrics.Tool
             HashTextBox.Text = null;
             CoverPictureBox.BackgroundImage = null;
             DirTextBox.Text = null;
+            DirTimeLabel.Text = null;
             SongNameTextBox.Text = null;
             SubNameTextBox.Text = null;
             SongAuthorTextBox.Text = null;
@@ -99,6 +107,7 @@ namespace BeatLyrics.Tool
 
             CoverPictureBox.BackgroundImage = item.CoverImage;
             DirTextBox.Text = item.Directory;
+            DirTimeLabel.Text = item.DirectoryTimeText;
             SongNameTextBox.Text = item.SongName;
             SubNameTextBox.Text = item.SongSubName;
             SongAuthorTextBox.Text = item.SongAuthor;
@@ -137,7 +146,13 @@ namespace BeatLyrics.Tool
                     .ToArray();
             }
 
-            items = items.Where(p => p.IsVerify == SearchVerifyCheckBox.Checked).ToArray();
+            if (false == All2RadioButton.Checked)
+            {
+                items = items
+                    .Where(p => p.IsVerify == VerifyRadioButton.Checked)
+                    .ToArray();
+            }
+
 
             MainObjectListView.SetObjects(items);
 
@@ -154,6 +169,7 @@ namespace BeatLyrics.Tool
 
             MainObjectListView.AllColumns.Add(new OLVColumn(nameof(LevelInfo.SongAuthor), nameof(LevelInfo.SongAuthor)) { Width = 128, IsTileViewColumn = true });
             MainObjectListView.AllColumns.Add(new OLVColumn(nameof(LevelInfo.LevelAuthor), nameof(LevelInfo.LevelAuthor)) { Width = 128, IsTileViewColumn = true });
+            MainObjectListView.AllColumns.Add(new OLVColumn(nameof(LevelInfo.DirectoryTime), nameof(LevelInfo.DirectoryTimeText)) { Width = 128, IsTileViewColumn = true });
 
             MainObjectListView.RebuildColumns();
 

+ 7 - 2
BeatLyrics.Tool/Models/LevelInfo.cs

@@ -1,6 +1,7 @@
-using System.Drawing;
+using BeatLyrics.Common;
+using System;
+using System.Drawing;
 using System.IO;
-using BeatLyrics.Common;
 
 namespace BeatLyrics.Tool.Models
 {
@@ -26,5 +27,9 @@ namespace BeatLyrics.Tool.Models
         public Image CoverImage { get; set; }
 
         public LocalLyricInfo[] LocalLyrics { get; set; }
+
+        public DateTime DirectoryTime { get; set; }
+
+        public string DirectoryTimeText => DirectoryTime.ToString("yyyy-MM-dd HH:mm:ss");
     }
 }

+ 4 - 0
BeatLyrics.Tool/Models/TextTuple.cs

@@ -9,6 +9,10 @@
         public override string ToString()
         {
             var left = 0;
+
+            var regexKata = new System.Text.RegularExpressions.Regex("^[\\p{IsKatakana}]+$");
+            if (regexKata.IsMatch(Text)) return Text;
+
             if (Ruby == Text) return Text;
 
             if (false == string.IsNullOrWhiteSpace(Ruby))

File diff suppressed because it is too large
+ 1722 - 0
BeatLyrics.Tool/RefLibs/DSPLib.cs


+ 848 - 0
BeatLyrics.Tool/RefLibs/ZzzzRangeBar.cs

@@ -0,0 +1,848 @@
+//SOURCE: https://www.codeproject.com/Articles/2275/C-RangeBar-control
+
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace BeatLyrics.Tool.UserControls
+{
+    /// <summary>
+    /// The ZzzzRangeBar class describes a slide control with two buttons.
+    /// A number range is assigned to the control and with the two slide
+    /// buttons you can select an interval inside the range. This control can
+    /// p.e. used for threshold setting in an image processing tool.
+    /// If you push with left mouse button on a slide button it will marked and
+    /// while mouse button is pressed you can move the slider left and right.
+    /// Otherwise you can use the keys + and - to manipulate the slider position.
+    /// The control will throw two events. While left mouse button is pressed and the
+    /// position of one slider has changed the event OnRangeChanging will generate and
+    /// if you release mouse button, the event OnRangeChanged signals program that
+    /// a new range was selected.
+    /// </summary>
+    public class ZzzzRangeBar : System.Windows.Forms.UserControl
+    {
+        // delegate to handle range changed
+        public delegate void RangeChangedEventHandler(object sender, EventArgs e);
+
+        // delegate to handle range is changing
+        public delegate void RangeChangingEventHandler(object sender, EventArgs e);
+
+        /// <summary>
+        /// designer variable
+        /// </summary>
+        private System.ComponentModel.Container components = null;
+
+        public ZzzzRangeBar()
+        {
+            // Dieser Aufruf ist für den Windows Form-Designer erforderlich.
+            InitializeComponent();
+        }
+
+        /// <summary>
+        /// Die verwendeten Ressourcen bereinigen.
+        /// </summary>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                if (components != null)
+                {
+                    components.Dispose();
+                }
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Component Designer generated code
+
+        /// <summary>
+        /// Erforderliche Methode für die Designerunterstützung.
+        /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            //
+            // ZzzzRangeBar
+            //
+            this.Name = "ZzzzRangeBar";
+            this.Size = new System.Drawing.Size(344, 64);
+            this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.OnKeyPress);
+            this.Resize += new System.EventHandler(this.OnResize);
+            this.Load += new System.EventHandler(this.OnLoad);
+            this.SizeChanged += new System.EventHandler(this.OnSizeChanged);
+            this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.OnMouseUp);
+            this.Paint += new System.Windows.Forms.PaintEventHandler(this.OnPaint);
+            this.Leave += new System.EventHandler(this.OnLeave);
+            this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.OnMouseMove);
+            this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnMouseDown);
+        }
+
+        #endregion Component Designer generated code
+
+        public enum ActiveMarkType { none, left, right };
+
+        public enum RangeBarOrientation { horizontal, vertical };
+
+        public enum TopBottomOrientation { top, bottom, both };
+
+        private Color colorInner = Color.LightGreen;
+        private Color colorRange = Color.FromKnownColor(KnownColor.Control);
+        private Color colorShadowLight = Color.FromKnownColor(KnownColor.ControlLightLight);
+        private Color colorShadowDark = Color.FromKnownColor(KnownColor.ControlDarkDark);
+        private int sizeShadow = 1;
+        private double Minimum = 0;
+        private double Maximum = 10;
+        private double rangeMin = 3;
+        private double rangeMax = 5;
+        private ActiveMarkType ActiveMark = ActiveMarkType.none;
+
+        private RangeBarOrientation orientationBar = RangeBarOrientation.horizontal; // orientation of range bar
+        private TopBottomOrientation orientationScale = TopBottomOrientation.bottom;
+        private int BarHeight = 8;      // Height of Bar
+        private int MarkWidth = 8;      // Width of mark knobs
+        private int MarkHeight = 24;    // total height of mark knobs
+        private int TickHeight = 6; // Height of axis tick
+        private int numAxisDivision = 10;
+
+        private int PosL = 0, PosR = 0; // Pixel-Position of mark buttons
+        private int XPosMin, XPosMax;
+
+        private Point[] LMarkPnt = new Point[5];
+        private Point[] RMarkPnt = new Point[5];
+
+        private bool MoveLMark = false;
+        private bool MoveRMark = false;
+
+        //------------------------------------
+        // Properties
+        //------------------------------------
+
+        /// <summary>
+        /// set or get tick height
+        /// </summary>
+        public int HeightOfTick
+        {
+            set
+            {
+                TickHeight = Math.Min(Math.Max(1, value), BarHeight);
+                Invalidate();
+                Update();
+            }
+            get
+            {
+                return TickHeight;
+            }
+        }
+
+        /// <summary>
+        /// set or get mark knob height
+        /// </summary>
+        public int HeightOfMark
+        {
+            set
+            {
+                MarkHeight = Math.Max(BarHeight + 2, value);
+                Invalidate();
+                Update();
+            }
+            get
+            {
+                return MarkHeight;
+            }
+        }
+
+        /// <summary>
+        /// set/get height of mark
+        /// </summary>
+        public int HeightOfBar
+        {
+            set
+            {
+                BarHeight = Math.Min(value, MarkHeight - 2);
+                Invalidate();
+                Update();
+            }
+            get
+            {
+                return BarHeight;
+            }
+        }
+
+        /// <summary>
+        /// set or get range bar orientation
+        /// </summary>
+        public RangeBarOrientation Orientation
+        {
+            set
+            {
+                orientationBar = value;
+                Invalidate();
+                Update();
+            }
+            get
+            {
+                return orientationBar;
+            }
+        }
+
+        /// <summary>
+        /// set or get scale orientation
+        /// </summary>
+        public TopBottomOrientation ScaleOrientation
+        {
+            set
+            {
+                orientationScale = value;
+                Invalidate();
+                Update();
+            }
+            get
+            {
+                return orientationScale;
+            }
+        }
+
+        /// <summary>
+        ///  set or get right side of range
+        /// </summary>
+        public int RangeMaximum
+        {
+            set
+            {
+                rangeMax = (double)value;
+                if (rangeMax < Minimum)
+                    rangeMax = Minimum;
+                else if (rangeMax > Maximum)
+                    rangeMax = Maximum;
+                if (rangeMax < rangeMin)
+                    rangeMax = rangeMin;
+                Range2Pos();
+                Invalidate(true);
+            }
+            get { return (int)rangeMax; }
+        }
+
+        /// <summary>
+        /// set or get left side of range
+        /// </summary>
+        public int RangeMinimum
+        {
+            set
+            {
+                rangeMin = (double)value;
+                if (rangeMin < Minimum)
+                    rangeMin = Minimum;
+                else if (rangeMin > Maximum)
+                    rangeMin = Maximum;
+                if (rangeMin > rangeMax)
+                    rangeMin = rangeMax;
+                Range2Pos();
+                Invalidate(true);
+            }
+            get
+            {
+                return (int)rangeMin;
+            }
+        }
+
+        /// <summary>
+        /// set or get right side of total range
+        /// </summary>
+        public int TotalMaximum
+        {
+            set
+            {
+                Maximum = (double)value;
+                if (rangeMax > Maximum)
+                    rangeMax = Maximum;
+                Range2Pos();
+                Invalidate(true);
+            }
+            get { return (int)Maximum; }
+        }
+
+        /// <summary>
+        /// set or get left side of total range
+        /// </summary>
+        public int TotalMinimum
+        {
+            set
+            {
+                Minimum = (double)value;
+                if (rangeMin < Minimum)
+                    rangeMin = Minimum;
+                Range2Pos();
+                Invalidate(true);
+            }
+            get { return (int)Minimum; }
+        }
+
+        /// <summary>
+        /// set or get number of divisions
+        /// </summary>
+        public int DivisionNum
+        {
+            set
+            {
+                numAxisDivision = value;
+                Refresh();
+            }
+            get { return numAxisDivision; }
+        }
+
+        /// <summary>
+        /// set or get color of inner range
+        /// </summary>
+        public Color InnerColor
+        {
+            set
+            {
+                colorInner = value;
+                Refresh();
+            }
+            get { return colorInner; }
+        }
+
+        /// <summary>
+        /// set selected range
+        /// </summary>
+        /// <param name="left">left side of range</param>
+        /// <param name="right">right side of range</param>
+        public void SelectRange(int left, int right)
+        {
+            RangeMinimum = left;
+            RangeMaximum = right;
+            Range2Pos();
+            Invalidate(true);
+        }
+
+        /// <summary>
+        /// set range limits
+        /// </summary>
+        /// <param name="left">left side of range limit</param>
+        /// <param name="right">right side of range limit</param>
+        public void SetRangeLimit(double left, double right)
+        {
+            Minimum = left;
+            Maximum = right;
+            Range2Pos();
+            Invalidate(true);
+        }
+
+        // paint event reaction
+        private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e)
+        {
+            int h = this.Height;
+            int w = this.Width;
+            int baryoff, markyoff, tickyoff1, tickyoff2;
+            double dtick;
+            int tickpos;
+            Pen penRange = new Pen(colorRange);
+            Pen penShadowLight = new Pen(colorShadowLight);
+            Pen penShadowDark = new Pen(colorShadowDark);
+            SolidBrush brushShadowLight = new SolidBrush(colorShadowLight);
+            SolidBrush brushShadowDark = new SolidBrush(colorShadowDark);
+            SolidBrush brushInner;
+            SolidBrush brushRange = new SolidBrush(colorRange);
+
+            if (this.Enabled == true)
+                brushInner = new SolidBrush(colorInner);
+            else
+                brushInner = new SolidBrush(Color.FromKnownColor(KnownColor.InactiveCaption));
+
+            // range
+            XPosMin = MarkWidth + 1;
+            if (this.orientationBar == RangeBarOrientation.horizontal)
+                XPosMax = w - MarkWidth - 1;
+            else
+                XPosMax = h - MarkWidth - 1;
+
+            // range check
+            if (PosL < XPosMin) PosL = XPosMin;
+            if (PosL > XPosMax) PosL = XPosMax;
+            if (PosR > XPosMax) PosR = XPosMax;
+            if (PosR < XPosMin) PosR = XPosMin;
+
+            Range2Pos();
+
+            if (this.orientationBar == RangeBarOrientation.horizontal)
+            {
+                baryoff = (h - BarHeight) / 2;
+                markyoff = baryoff + (BarHeight - MarkHeight) / 2 - 1;
+
+                // total range bar frame
+                e.Graphics.FillRectangle(brushShadowDark, 0, baryoff, w - 1, sizeShadow);   // top
+                e.Graphics.FillRectangle(brushShadowDark, 0, baryoff, sizeShadow, BarHeight - 1);   // left
+                e.Graphics.FillRectangle(brushShadowLight, 0, baryoff + BarHeight - 1 - sizeShadow, w - 1, sizeShadow); // bottom
+                e.Graphics.FillRectangle(brushShadowLight, w - 1 - sizeShadow, baryoff, sizeShadow, BarHeight - 1); // right
+
+                // inner region
+                e.Graphics.FillRectangle(brushInner, PosL, baryoff + sizeShadow, PosR - PosL, BarHeight - 1 - 2 * sizeShadow);
+
+                // Skala
+                if (orientationScale == TopBottomOrientation.bottom)
+                {
+                    tickyoff1 = tickyoff2 = baryoff + BarHeight + 2;
+                }
+                else if (orientationScale == TopBottomOrientation.top)
+                {
+                    tickyoff1 = tickyoff2 = baryoff - TickHeight - 4;
+                }
+                else
+                {
+                    tickyoff1 = baryoff + BarHeight + 2;
+                    tickyoff2 = baryoff - TickHeight - 4;
+                }
+
+                if (numAxisDivision > 1)
+                {
+                    dtick = (double)(XPosMax - XPosMin) / (double)numAxisDivision;
+                    for (int i = 0; i < numAxisDivision + 1; i++)
+                    {
+                        tickpos = (int)Math.Round((double)i * dtick);
+                        if (orientationScale == TopBottomOrientation.bottom
+                            || orientationScale == TopBottomOrientation.both)
+                        {
+                            e.Graphics.DrawLine(penShadowDark, MarkWidth + 1 + tickpos,
+                                tickyoff1,
+                                MarkWidth + 1 + tickpos,
+                                tickyoff1 + TickHeight);
+                        }
+                        if (orientationScale == TopBottomOrientation.top
+                            || orientationScale == TopBottomOrientation.both)
+                        {
+                            e.Graphics.DrawLine(penShadowDark, MarkWidth + 1 + tickpos,
+                                tickyoff2,
+                                MarkWidth + 1 + tickpos,
+                                tickyoff2 + TickHeight);
+                        }
+                    }
+                }
+
+                // left mark knob
+                LMarkPnt[0].X = PosL - MarkWidth; LMarkPnt[0].Y = markyoff + MarkHeight / 3;
+                LMarkPnt[1].X = PosL; LMarkPnt[1].Y = markyoff;
+                LMarkPnt[2].X = PosL; LMarkPnt[2].Y = markyoff + MarkHeight;
+                LMarkPnt[3].X = PosL - MarkWidth; LMarkPnt[3].Y = markyoff + 2 * MarkHeight / 3;
+                LMarkPnt[4].X = PosL - MarkWidth; LMarkPnt[4].Y = markyoff;
+                e.Graphics.FillPolygon(brushRange, LMarkPnt);
+                e.Graphics.DrawLine(penShadowDark, LMarkPnt[3].X - 1, LMarkPnt[3].Y, LMarkPnt[1].X - 1, LMarkPnt[2].Y); // lower left shadow
+                e.Graphics.DrawLine(penShadowLight, LMarkPnt[0].X - 1, LMarkPnt[0].Y, LMarkPnt[0].X - 1, LMarkPnt[3].Y); // left shadow
+                e.Graphics.DrawLine(penShadowLight, LMarkPnt[0].X - 1, LMarkPnt[0].Y, LMarkPnt[1].X - 1, LMarkPnt[1].Y); // upper shadow
+                if (PosL < PosR)
+                    e.Graphics.DrawLine(penShadowDark, LMarkPnt[1].X, LMarkPnt[1].Y + 1, LMarkPnt[1].X, LMarkPnt[2].Y); // right shadow
+                if (ActiveMark == ActiveMarkType.left)
+                {
+                    e.Graphics.DrawLine(penShadowLight, PosL - MarkWidth / 2 - 1, markyoff + MarkHeight / 3, PosL - MarkWidth / 2 - 1, markyoff + 2 * MarkHeight / 3); // active mark
+                    e.Graphics.DrawLine(penShadowDark, PosL - MarkWidth / 2, markyoff + MarkHeight / 3, PosL - MarkWidth / 2, markyoff + 2 * MarkHeight / 3); // active mark
+                }
+
+                // right mark knob
+                RMarkPnt[0].X = PosR + MarkWidth; RMarkPnt[0].Y = markyoff + MarkHeight / 3;
+                RMarkPnt[1].X = PosR; RMarkPnt[1].Y = markyoff;
+                RMarkPnt[2].X = PosR; RMarkPnt[2].Y = markyoff + MarkHeight;
+                RMarkPnt[3].X = PosR + MarkWidth; RMarkPnt[3].Y = markyoff + 2 * MarkHeight / 3;
+                RMarkPnt[4].X = PosR + MarkWidth; RMarkPnt[4].Y = markyoff;
+                e.Graphics.FillPolygon(brushRange, RMarkPnt);
+                if (PosL < PosR)
+                    e.Graphics.DrawLine(penShadowLight, RMarkPnt[1].X - 1, RMarkPnt[1].Y + 1, RMarkPnt[2].X - 1, RMarkPnt[2].Y); // left shadow
+                e.Graphics.DrawLine(penShadowDark, RMarkPnt[2].X, RMarkPnt[2].Y, RMarkPnt[3].X, RMarkPnt[3].Y); // lower right shadow
+                e.Graphics.DrawLine(penShadowDark, RMarkPnt[0].X, RMarkPnt[0].Y, RMarkPnt[1].X, RMarkPnt[1].Y); // upper shadow
+                e.Graphics.DrawLine(penShadowDark, RMarkPnt[0].X, RMarkPnt[0].Y + 1, RMarkPnt[3].X, RMarkPnt[3].Y); // right shadow
+                if (ActiveMark == ActiveMarkType.right)
+                {
+                    e.Graphics.DrawLine(penShadowLight, PosR + MarkWidth / 2 - 1, markyoff + MarkHeight / 3, PosR + MarkWidth / 2 - 1, markyoff + 2 * MarkHeight / 3); // active mark
+                    e.Graphics.DrawLine(penShadowDark, PosR + MarkWidth / 2, markyoff + MarkHeight / 3, PosR + MarkWidth / 2, markyoff + 2 * MarkHeight / 3); // active mark
+                }
+
+                if (MoveLMark)
+                {
+                    Font fontMark = new Font("Arial", MarkWidth);
+                    SolidBrush brushMark = new SolidBrush(colorShadowDark);
+                    StringFormat strformat = new StringFormat();
+                    strformat.Alignment = StringAlignment.Center;
+                    strformat.LineAlignment = StringAlignment.Near;
+                    e.Graphics.DrawString(rangeMin.ToString(), fontMark, brushMark, PosL, tickyoff1 + TickHeight, strformat);
+                }
+
+                if (MoveRMark)
+                {
+                    Font fontMark = new Font("Arial", MarkWidth);
+                    SolidBrush brushMark = new SolidBrush(colorShadowDark);
+                    StringFormat strformat = new StringFormat();
+                    strformat.Alignment = StringAlignment.Center;
+                    strformat.LineAlignment = StringAlignment.Near;
+                    e.Graphics.DrawString(rangeMax.ToString(), fontMark, brushMark, PosR, tickyoff1 + TickHeight, strformat);
+                }
+            }
+            else // vertical bar
+            {
+                baryoff = (w + BarHeight) / 2;
+                markyoff = baryoff - BarHeight / 2 - MarkHeight / 2;
+                if (orientationScale == TopBottomOrientation.bottom)
+                {
+                    tickyoff1 = tickyoff2 = baryoff + 2;
+                }
+                else if (orientationScale == TopBottomOrientation.top)
+                {
+                    tickyoff1 = tickyoff2 = baryoff - BarHeight - 2 - TickHeight;
+                }
+                else
+                {
+                    tickyoff1 = baryoff + 2;
+                    tickyoff2 = baryoff - BarHeight - 2 - TickHeight;
+                }
+
+                // total range bar frame
+                e.Graphics.FillRectangle(brushShadowDark, baryoff - BarHeight, 0, BarHeight, sizeShadow);   // top
+                e.Graphics.FillRectangle(brushShadowDark, baryoff - BarHeight, 0, sizeShadow, h - 1);   // left
+                e.Graphics.FillRectangle(brushShadowLight, baryoff, 0, sizeShadow, h - 1);  // right
+                e.Graphics.FillRectangle(brushShadowLight, baryoff - BarHeight, h - sizeShadow, BarHeight, sizeShadow); // bottom
+
+                // inner region
+                e.Graphics.FillRectangle(brushInner, baryoff - BarHeight + sizeShadow, PosL, BarHeight - 2 * sizeShadow, PosR - PosL);
+
+                // Skala
+                if (numAxisDivision > 1)
+                {
+                    dtick = (double)(XPosMax - XPosMin) / (double)numAxisDivision;
+                    for (int i = 0; i < numAxisDivision + 1; i++)
+                    {
+                        tickpos = (int)Math.Round((double)i * dtick);
+                        if (orientationScale == TopBottomOrientation.bottom
+                            || orientationScale == TopBottomOrientation.both)
+                            e.Graphics.DrawLine(penShadowDark,
+                                tickyoff1,
+                                MarkWidth + 1 + tickpos,
+                                tickyoff1 + TickHeight,
+                                MarkWidth + 1 + tickpos
+                                );
+                        if (orientationScale == TopBottomOrientation.top
+                            || orientationScale == TopBottomOrientation.both)
+                            e.Graphics.DrawLine(penShadowDark,
+                                tickyoff2,
+                                MarkWidth + 1 + tickpos,
+                                tickyoff2 + TickHeight,
+                                MarkWidth + 1 + tickpos
+                                );
+                    }
+                }
+
+                // left(upper) mark knob
+                LMarkPnt[0].Y = PosL - MarkWidth; LMarkPnt[0].X = markyoff + MarkHeight / 3;
+                LMarkPnt[1].Y = PosL; LMarkPnt[1].X = markyoff;
+                LMarkPnt[2].Y = PosL; LMarkPnt[2].X = markyoff + MarkHeight;
+                LMarkPnt[3].Y = PosL - MarkWidth; LMarkPnt[3].X = markyoff + 2 * MarkHeight / 3;
+                LMarkPnt[4].Y = PosL - MarkWidth; LMarkPnt[4].X = markyoff;
+                e.Graphics.FillPolygon(brushRange, LMarkPnt);
+                e.Graphics.DrawLine(penShadowDark, LMarkPnt[3].X, LMarkPnt[3].Y, LMarkPnt[2].X, LMarkPnt[2].Y); // right shadow
+                e.Graphics.DrawLine(penShadowLight, LMarkPnt[0].X - 1, LMarkPnt[0].Y, LMarkPnt[3].X - 1, LMarkPnt[3].Y); // top shadow
+                e.Graphics.DrawLine(penShadowLight, LMarkPnt[0].X - 1, LMarkPnt[0].Y, LMarkPnt[1].X - 1, LMarkPnt[1].Y); // left shadow
+                if (PosL < PosR)
+                    e.Graphics.DrawLine(penShadowDark, LMarkPnt[1].X, LMarkPnt[1].Y, LMarkPnt[2].X, LMarkPnt[2].Y); // lower shadow
+                if (ActiveMark == ActiveMarkType.left)
+                {
+                    e.Graphics.DrawLine(penShadowLight, markyoff + MarkHeight / 3, PosL - MarkWidth / 2, markyoff + 2 * MarkHeight / 3, PosL - MarkWidth / 2); // active mark
+                    e.Graphics.DrawLine(penShadowDark, markyoff + MarkHeight / 3, PosL - MarkWidth / 2 + 1, markyoff + 2 * MarkHeight / 3, PosL - MarkWidth / 2 + 1); // active mark
+                }
+
+                // right mark knob
+                RMarkPnt[0].Y = PosR + MarkWidth; RMarkPnt[0].X = markyoff + MarkHeight / 3;
+                RMarkPnt[1].Y = PosR; RMarkPnt[1].X = markyoff;
+                RMarkPnt[2].Y = PosR; RMarkPnt[2].X = markyoff + MarkHeight;
+                RMarkPnt[3].Y = PosR + MarkWidth; RMarkPnt[3].X = markyoff + 2 * MarkHeight / 3;
+                RMarkPnt[4].Y = PosR + MarkWidth; RMarkPnt[4].X = markyoff;
+                e.Graphics.FillPolygon(brushRange, RMarkPnt);
+                e.Graphics.DrawLine(penShadowDark, RMarkPnt[2].X, RMarkPnt[2].Y, RMarkPnt[3].X, RMarkPnt[3].Y); // lower right shadow
+                e.Graphics.DrawLine(penShadowDark, RMarkPnt[0].X, RMarkPnt[0].Y, RMarkPnt[1].X, RMarkPnt[1].Y); // upper shadow
+                e.Graphics.DrawLine(penShadowDark, RMarkPnt[0].X, RMarkPnt[0].Y, RMarkPnt[3].X, RMarkPnt[3].Y); // right shadow
+                if (PosL < PosR)
+                    e.Graphics.DrawLine(penShadowLight, RMarkPnt[1].X, RMarkPnt[1].Y, RMarkPnt[2].X, RMarkPnt[2].Y); // left shadow
+                if (ActiveMark == ActiveMarkType.right)
+                {
+                    e.Graphics.DrawLine(penShadowLight, markyoff + MarkHeight / 3, PosR + MarkWidth / 2 - 1, markyoff + 2 * MarkHeight / 3, PosR + MarkWidth / 2 - 1); // active mark
+                    e.Graphics.DrawLine(penShadowDark, markyoff + MarkHeight / 3, PosR + MarkWidth / 2, markyoff + 2 * MarkHeight / 3, PosR + MarkWidth / 2); // active mark
+                }
+
+                if (MoveLMark)
+                {
+                    Font fontMark = new Font("Arial", MarkWidth);
+                    SolidBrush brushMark = new SolidBrush(colorShadowDark);
+                    StringFormat strformat = new StringFormat();
+                    strformat.Alignment = StringAlignment.Near;
+                    strformat.LineAlignment = StringAlignment.Center;
+                    e.Graphics.DrawString(rangeMin.ToString(), fontMark, brushMark, tickyoff1 + TickHeight + 2, PosL, strformat);
+                }
+
+                if (MoveRMark)
+                {
+                    Font fontMark = new Font("Arial", MarkWidth);
+                    SolidBrush brushMark = new SolidBrush(colorShadowDark);
+                    StringFormat strformat = new StringFormat();
+                    strformat.Alignment = StringAlignment.Near;
+                    strformat.LineAlignment = StringAlignment.Center;
+                    e.Graphics.DrawString(rangeMax.ToString(), fontMark, brushMark, tickyoff1 + TickHeight, PosR, strformat);
+                }
+            }
+        }
+
+        // mouse down event
+        private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
+        {
+            if (this.Enabled)
+            {
+                Rectangle LMarkRect = new Rectangle(
+                    Math.Min(LMarkPnt[0].X, LMarkPnt[1].X),     // X
+                    Math.Min(LMarkPnt[0].Y, LMarkPnt[3].Y),     // Y
+                    Math.Abs(LMarkPnt[2].X - LMarkPnt[0].X),        // width
+                    Math.Max(Math.Abs(LMarkPnt[0].Y - LMarkPnt[3].Y), Math.Abs(LMarkPnt[0].Y - LMarkPnt[1].Y)));    // height
+                Rectangle RMarkRect = new Rectangle(
+                    Math.Min(RMarkPnt[0].X, RMarkPnt[2].X),     // X
+                    Math.Min(RMarkPnt[0].Y, RMarkPnt[1].Y),     // Y
+                    Math.Abs(RMarkPnt[0].X - RMarkPnt[2].X),        // width
+                    Math.Max(Math.Abs(RMarkPnt[2].Y - RMarkPnt[0].Y), Math.Abs(RMarkPnt[1].Y - RMarkPnt[0].Y)));        // height
+
+                if (LMarkRect.Contains(e.X, e.Y))
+                {
+                    this.Capture = true;
+                    MoveLMark = true;
+                    ActiveMark = ActiveMarkType.left;
+                    Invalidate(true);
+                }
+
+                if (RMarkRect.Contains(e.X, e.Y))
+                {
+                    this.Capture = true;
+                    MoveRMark = true;
+                    ActiveMark = ActiveMarkType.right;
+                    Invalidate(true);
+                }
+            }
+        }
+
+        // mouse up event
+        private void OnMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
+        {
+            if (this.Enabled)
+            {
+                this.Capture = false;
+
+                MoveLMark = false;
+                MoveRMark = false;
+
+                Invalidate();
+
+                OnRangeChanged(EventArgs.Empty);
+            }
+        }
+
+        // mouse move event
+        private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
+        {
+            if (this.Enabled)
+            {
+                int h = this.Height;
+                int w = this.Width;
+                double r1 = (double)rangeMin * (double)w / (double)(Maximum - Minimum);
+                double r2 = (double)rangeMax * (double)w / (double)(Maximum - Minimum);
+                Rectangle LMarkRect = new Rectangle(
+                    Math.Min(LMarkPnt[0].X, LMarkPnt[1].X),     // X
+                    Math.Min(LMarkPnt[0].Y, LMarkPnt[3].Y),     // Y
+                    Math.Abs(LMarkPnt[2].X - LMarkPnt[0].X),        // width
+                    Math.Max(Math.Abs(LMarkPnt[0].Y - LMarkPnt[3].Y), Math.Abs(LMarkPnt[0].Y - LMarkPnt[1].Y)));    // height
+                Rectangle RMarkRect = new Rectangle(
+                    Math.Min(RMarkPnt[0].X, RMarkPnt[2].X),     // X
+                    Math.Min(RMarkPnt[0].Y, RMarkPnt[1].Y),     // Y
+                    Math.Abs(RMarkPnt[0].X - RMarkPnt[2].X),        // width
+                    Math.Max(Math.Abs(RMarkPnt[2].Y - RMarkPnt[0].Y), Math.Abs(RMarkPnt[1].Y - RMarkPnt[0].Y)));        // height
+
+                if (LMarkRect.Contains(e.X, e.Y) || RMarkRect.Contains(e.X, e.Y))
+                {
+                    if (this.orientationBar == RangeBarOrientation.horizontal)
+                        this.Cursor = Cursors.SizeWE;
+                    else
+                        this.Cursor = Cursors.SizeNS;
+                }
+                else this.Cursor = Cursors.Arrow;
+
+                if (MoveLMark)
+                {
+                    if (this.orientationBar == RangeBarOrientation.horizontal)
+                        this.Cursor = Cursors.SizeWE;
+                    else
+                        this.Cursor = Cursors.SizeNS;
+                    if (this.orientationBar == RangeBarOrientation.horizontal)
+                        PosL = e.X;
+                    else
+                        PosL = e.Y;
+                    if (PosL < XPosMin)
+                        PosL = XPosMin;
+                    if (PosL > XPosMax)
+                        PosL = XPosMax;
+                    if (PosR < PosL)
+                        PosR = PosL;
+                    Pos2Range();
+                    ActiveMark = ActiveMarkType.left;
+                    Invalidate(true);
+
+                    OnRangeChanging(EventArgs.Empty);
+                }
+                else if (MoveRMark)
+                {
+                    if (this.orientationBar == RangeBarOrientation.horizontal)
+                        this.Cursor = Cursors.SizeWE;
+                    else
+                        this.Cursor = Cursors.SizeNS;
+                    if (this.orientationBar == RangeBarOrientation.horizontal)
+                        PosR = e.X;
+                    else
+                        PosR = e.Y;
+                    if (PosR > XPosMax)
+                        PosR = XPosMax;
+                    if (PosR < XPosMin)
+                        PosR = XPosMin;
+                    if (PosL > PosR)
+                        PosL = PosR;
+                    Pos2Range();
+                    ActiveMark = ActiveMarkType.right;
+                    Invalidate(true);
+
+                    OnRangeChanging(EventArgs.Empty);
+                }
+            }
+        }
+
+        /// <summary>
+        ///  transform pixel position to range position
+        /// </summary>
+        private void Pos2Range()
+        {
+            int w;
+            int posw;
+
+            if (this.orientationBar == RangeBarOrientation.horizontal)
+                w = this.Width;
+            else
+                w = this.Height;
+            posw = w - 2 * MarkWidth - 2;
+
+            rangeMin = Minimum + (int)Math.Round((double)(Maximum - Minimum) * (double)(PosL - XPosMin) / (double)posw);
+            rangeMax = Minimum + (int)Math.Round((double)(Maximum - Minimum) * (double)(PosR - XPosMin) / (double)posw);
+        }
+
+        /// <summary>
+        ///  transform range position to pixel position
+        /// </summary>
+        private void Range2Pos()
+        {
+            int w;
+            int posw;
+
+            if (this.orientationBar == RangeBarOrientation.horizontal)
+                w = this.Width;
+            else
+                w = this.Height;
+            posw = w - 2 * MarkWidth - 2;
+
+            PosL = XPosMin + (int)Math.Round((double)posw * (double)(rangeMin - Minimum) / (double)(Maximum - Minimum));
+            PosR = XPosMin + (int)Math.Round((double)posw * (double)(rangeMax - Minimum) / (double)(Maximum - Minimum));
+        }
+
+        /// <summary>
+        /// method to handle resize event
+        /// </summary>
+        /// <param name="sender">object that sends event to resize</param>
+        /// <param name="e">event parameter</param>
+        private void OnResize(object sender, System.EventArgs e)
+        {
+            //Range2Pos();
+            Invalidate(true);
+        }
+
+        /// <summary>
+        /// method to handle key pressed event
+        /// </summary>
+        /// <param name="sender">object that sends key pressed event</param>
+        /// <param name="e">event parameter</param>
+        private void OnKeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
+        {
+            if (this.Enabled)
+            {
+                if (ActiveMark == ActiveMarkType.left)
+                {
+                    if (e.KeyChar == '+')
+                    {
+                        rangeMin++;
+                        if (rangeMin > Maximum)
+                            rangeMin = Maximum;
+                        if (rangeMax < rangeMin)
+                            rangeMax = rangeMin;
+                        OnRangeChanged(EventArgs.Empty);
+                    }
+                    else if (e.KeyChar == '-')
+                    {
+                        rangeMin--;
+                        if (rangeMin < Minimum)
+                            rangeMin = Minimum;
+                        OnRangeChanged(EventArgs.Empty);
+                    }
+                }
+                else if (ActiveMark == ActiveMarkType.right)
+                {
+                    if (e.KeyChar == '+')
+                    {
+                        rangeMax++;
+                        if (rangeMax > Maximum)
+                            rangeMax = Maximum;
+                        OnRangeChanged(EventArgs.Empty);
+                    }
+                    else if (e.KeyChar == '-')
+                    {
+                        rangeMax--;
+                        if (rangeMax < Minimum)
+                            rangeMax = Minimum;
+                        if (rangeMax < rangeMin)
+                            rangeMin = rangeMax;
+                        OnRangeChanged(EventArgs.Empty);
+                    }
+                }
+                Invalidate(true);
+            }
+        }
+
+        private void OnLoad(object sender, System.EventArgs e)
+        {
+            // use double buffering
+            SetStyle(ControlStyles.DoubleBuffer, true);
+            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
+            SetStyle(ControlStyles.UserPaint, true);
+        }
+
+        private void OnSizeChanged(object sender, System.EventArgs e)
+        {
+            Invalidate(true);
+            Update();
+        }
+
+        private void OnLeave(object sender, System.EventArgs e)
+        {
+            ActiveMark = ActiveMarkType.none;
+        }
+
+        public event RangeChangedEventHandler RangeChanged; // event handler for range changed
+
+        public event RangeChangedEventHandler RangeChanging; // event handler for range is changing
+
+        public virtual void OnRangeChanged(EventArgs e)
+        {
+            if (RangeChanged != null)
+                RangeChanged(this, e);
+        }
+
+        public virtual void OnRangeChanging(EventArgs e)
+        {
+            if (RangeChanging != null)
+                RangeChanging(this, e);
+        }
+    }
+}

+ 42 - 0
BeatLyrics.Tool/RefLibs/ZzzzRangeBar.resx

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<root>
+	<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+		<xsd:element name="root" msdata:IsDataSet="true">
+			<xsd:complexType>
+				<xsd:choice maxOccurs="unbounded">
+					<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" />
+							<xsd:attribute name="type" type="xsd:string" />
+							<xsd:attribute name="mimetype" type="xsd:string" />
+						</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>1.0.0.0</value>
+	</resheader>
+	<resheader name="Reader">
+		<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.3102.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+	</resheader>
+	<resheader name="Writer">
+		<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.3102.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+	</resheader>
+</root>

+ 4 - 2
BeatLyrics.Tool/UserControls/TextArrangeUserControl.Designer.cs

@@ -110,7 +110,8 @@
             this.LeftLyricsObjectListView.TabIndex = 0;
             this.LeftLyricsObjectListView.UseCompatibleStateImageBehavior = false;
             this.LeftLyricsObjectListView.View = System.Windows.Forms.View.Details;
-            this.LeftLyricsObjectListView.DoubleClick += new System.EventHandler(this.LeftLyricsObjectListView_DoubleClick);
+            this.LeftLyricsObjectListView.DoubleClick += new System.EventHandler(this.LyricsObjectListView_DoubleClick);
+            this.LeftLyricsObjectListView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.LyricsObjectListView_KeyDown);
             // 
             // olvColumn5
             // 
@@ -150,7 +151,8 @@
             this.RightLyricsObjectListView.TabIndex = 1;
             this.RightLyricsObjectListView.UseCompatibleStateImageBehavior = false;
             this.RightLyricsObjectListView.View = System.Windows.Forms.View.Details;
-            this.RightLyricsObjectListView.DoubleClick += new System.EventHandler(this.RightLyricsObjectListView_DoubleClick);
+            this.RightLyricsObjectListView.DoubleClick += new System.EventHandler(this.LyricsObjectListView_DoubleClick);
+            this.RightLyricsObjectListView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.LyricsObjectListView_KeyDown);
             // 
             // olvColumn8
             // 

+ 33 - 11
BeatLyrics.Tool/UserControls/TextArrangeUserControl.cs

@@ -1,16 +1,20 @@
 using BeatLyrics.Common.Models;
+using BeatLyrics.Tool.Dialogs;
 using BeatLyrics.Tool.Models;
 using BeatLyrics.Tool.Utils;
+using BrightIdeasSoftware;
 using Microsoft.International.Converters.TraditionalChineseToSimplifiedConverter;
 using System;
 using System.Linq;
+using System.Text.RegularExpressions;
 using System.Windows.Forms;
-using BeatLyrics.Tool.Dialogs;
 
 namespace BeatLyrics.Tool.UserControls
 {
     internal partial class TextArrangeUserControl : UserControl
     {
+        private static readonly Regex _reg = new Regex("([(\\(]\\p{IsHiragana}+[\\))])", RegexOptions.Compiled);
+
         private LyricFileExt _file;
 
         public TextArrangeUserControl()
@@ -120,7 +124,9 @@ namespace BeatLyrics.Tool.UserControls
             PosLabel.Text = timeSpan.FormatToTotalMinuteAndSecondsAndMs();
 
             var milliseconds = (int)timeSpan.TotalMilliseconds;
-            PlayPosTrackBar.Value = milliseconds;
+            PlayPosTrackBar.Value = milliseconds > PlayPosTrackBar.Maximum 
+                ? PlayPosTrackBar.Maximum 
+                : milliseconds;
 
             if (_file == null)
             {
@@ -168,10 +174,9 @@ namespace BeatLyrics.Tool.UserControls
 
         private void RemoveRubyButton_Click(object sender, EventArgs e)
         {
-            var reg = new System.Text.RegularExpressions.Regex("([(\\(]\\p{IsHiragana}+[\\))])", System.Text.RegularExpressions.RegexOptions.Compiled);
             foreach (LyricDetailExt item in LeftLyricsObjectListView.SelectedObjects)
             {
-                item.Text = reg.Replace(item.Text, "");
+                item.Text = _reg.Replace(item.Text, "");
                 LeftLyricsObjectListView.UpdateObject(item);
             }
         }
@@ -210,7 +215,11 @@ namespace BeatLyrics.Tool.UserControls
 
             foreach (var detail in _file.Subtitle)
             {
-                detail.Text = detail.Text.Trim('【', '】');
+                detail.Text = detail.Text
+                    .Trim('【', '】')
+                    .Trim('(', ')')
+                    .Trim('(', ')')
+                    ;
             }
 
             RightLyricsObjectListView.UpdateObjects(_file.Subtitle);
@@ -237,16 +246,29 @@ namespace BeatLyrics.Tool.UserControls
             RightLyricsObjectListView.SetObjects(_file.Subtitle);
         }
 
-        private void LeftLyricsObjectListView_DoubleClick(object sender, EventArgs e)
+        private void LyricsObjectListView_DoubleClick(object sender, EventArgs e)
         {
-            if (LeftLyricsObjectListView.SelectedObject != null)
-                OggAudioPlayer.Position = TimeSpan.FromMilliseconds(((LyricDetail)LeftLyricsObjectListView.SelectedObject).TimeMs);
+            var olv = (ObjectListView)sender;
+            if (olv.SelectedObject != null)
+                OggAudioPlayer.Position = TimeSpan.FromMilliseconds(((LyricDetail)olv.SelectedObject).TimeMs);
         }
 
-        private void RightLyricsObjectListView_DoubleClick(object sender, EventArgs e)
+        private void LyricsObjectListView_KeyDown(object sender, KeyEventArgs e)
         {
-            if (RightLyricsObjectListView.SelectedObject != null)
-                OggAudioPlayer.Position = TimeSpan.FromMilliseconds(((LyricDetail)RightLyricsObjectListView.SelectedObject).TimeMs);
+            var olv = (ObjectListView)sender;
+
+            if (e.KeyCode == Keys.F2 && olv.SelectedObject != null)
+            {
+                var item = (LyricDetailExt)olv.SelectedObject;
+                ContextDialog.PopTextBox(MousePosition, item.Text, (r, s) =>
+                {
+                    if (r == DialogResult.OK)
+                    {
+                        item.Text = s;
+                        olv.UpdateObject(item);
+                    }
+                });
+            }
         }
     }
 }

+ 94 - 54
BeatLyrics.Tool/UserControls/TimelineEditorUserControl.cs

@@ -1,5 +1,6 @@
 using BeatLyrics.Tool.Dialogs;
 using BeatLyrics.Tool.Models;
+using BeatLyrics.Tool.Utils;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
@@ -20,6 +21,9 @@ namespace BeatLyrics.Tool.UserControls
         private bool _lockDown;
         private int _playPos;
         private float _displayScale = 0.1f;
+        private SpectrumAnalyzer.SaResult[] _spectrumData;
+
+        private int _spectrumBlockInMs;
 
         private enum EditSide
         {
@@ -69,6 +73,14 @@ namespace BeatLyrics.Tool.UserControls
             }
         }
 
+        public double MinSaFreq { get; set; } = 80;
+
+        public double MaxSaFreq { get; set; } = 2000;
+
+        public double MinSaValue { get; set; } = -40;
+
+        public double MaxSaValue { get; set; } = 0;
+
         public event EventHandler PosChangeBegin;
 
         public event EventHandler<int> PosChange;
@@ -87,6 +99,14 @@ namespace BeatLyrics.Tool.UserControls
 
         public string GetPlayText() => GetPlayItem()?.Text ?? "";
 
+        public void SetSpectrumData(SpectrumAnalyzer.SaResult[] sa, int blockInMs)
+        {
+            _spectrumData = sa;
+            _spectrumBlockInMs = blockInMs;
+
+            Invalidate();
+        }
+
         public EditorUserControl()
         {
             DoubleBuffered = true;
@@ -123,13 +143,70 @@ namespace BeatLyrics.Tool.UserControls
             var t = TimeSpan.FromMilliseconds(Math.Abs(PlayPos));
             var strPos = PlayPos < 0 ? $"-{t.Minutes:00}:{t.Seconds:00}.{t.Milliseconds:000}" : $"{t.Minutes:00}:{t.Seconds:00}.{t.Milliseconds:000}";
             var strPosSize = e.Graphics.MeasureString(strPos, Font);
-            e.Graphics.DrawString(strPos, Font, Brushes.Black, mid - strPosSize.Width / 2, 0);
-            e.Graphics.DrawLine(Pens.Red, mid, strPosSize.Height, mid, Height);
 
             var offsetLeft = -PlayPos * DisplayScale + mid;
             var offsetTop = strPosSize.Height;
 
             e.Graphics.TranslateTransform(offsetLeft, offsetTop);
+
+            if (_spectrumData != null) // Draw Spectrum background
+            {
+                var msDurHalf = Width / DisplayScale / 2;
+
+                var beginBlockIndex = (int)(PlayPos - msDurHalf) / _spectrumBlockInMs;
+                var endBlockIndex = (int)(PlayPos + msDurHalf) / _spectrumBlockInMs;
+
+                if (beginBlockIndex < 0) beginBlockIndex = 0;
+                if (endBlockIndex > _spectrumData.Length) endBlockIndex = _spectrumData.Length;
+
+                var scaleHeight = Height - offsetTop;
+                var valueRange = MaxSaValue - MinSaValue;
+
+                var freqRange = MaxSaFreq - MinSaFreq;
+
+                var blockWidth = _spectrumBlockInMs * DisplayScale;
+
+                for (var i = beginBlockIndex; i < endBlockIndex; i++)
+                {
+                    var posX = i * blockWidth;
+
+                    var block = _spectrumData[i];
+
+                    var freqArray = block.FreqAxis.Where(p => p > MinSaFreq && p < MaxSaFreq).ToArray();
+                    var height = scaleHeight / freqArray.Length;
+
+                    var rects = new Tuple<RectangleF, byte>[freqArray.Length];
+                    for (var index = 0; index < freqArray.Length; index++)
+                    {
+                        var freq = freqArray[index];
+
+                        var val = block.Values[Array.IndexOf(block.FreqAxis, freq)];
+
+                        var posY = scaleHeight * (1 - (freq - MinSaFreq) / freqRange);
+
+                        if (val < MinSaValue) continue;
+
+                        byte color;
+                        if (val > MaxSaValue) color = 255;
+                        else
+                        {
+                            var ratio = (val - MinSaValue) / valueRange;
+                            color = (byte)(255 * ratio);
+                        }
+
+                        rects[index] = new Tuple<RectangleF, byte>(
+                            new RectangleF(posX, (float)posY, blockWidth, height), color
+                        );
+                    }
+
+                    foreach (var item in rects.Where(p => p != null).GroupBy(p => p.Item2).Where(p => p.Key > 0))
+                    {
+                        using Brush brush = new SolidBrush(Color.FromArgb(255 - item.Key, 255 - item.Key, 255));
+                        e.Graphics.FillRectangles(brush, item.Select(p => p.Item1).ToArray());
+                    }
+                }
+            }
+
             foreach (var item in Items)
             {
                 item.Box.X = (int)(item.TimeMs * DisplayScale);
@@ -155,57 +232,10 @@ namespace BeatLyrics.Tool.UserControls
                 item.Hit.Width = item.Box.Width;
                 item.Hit.Height = item.Box.Height;
             }
-        }
-
-        private void ShowTextBox(Point screenPoint, string orig, Action<DialogResult, string> ret)
-        {
-            var form = new Form
-            {
-                FormBorderStyle = FormBorderStyle.None,
-                StartPosition = FormStartPosition.Manual,
-                Location = screenPoint,
-                ShowInTaskbar = false,
-            };
-            var textBox = new TextBox
-            {
-                Left = 0,
-                Top = 0,
-            };
-
-            form.Controls.Add(textBox);
 
-            var closed = false;
-            textBox.PreviewKeyDown += delegate (object sender, PreviewKeyDownEventArgs args)
-            {
-                if (closed) return;
-                if (args.KeyCode == Keys.Escape)
-                {
-                    form.DialogResult = DialogResult.Cancel;
-                    form.Close();
-                }
-
-                if (args.KeyCode == Keys.Enter)
-                {
-                    form.DialogResult = DialogResult.OK;
-                    form.Close();
-                }
-            };
-            textBox.LostFocus += delegate
-            {
-                if (closed) return;
-                form.DialogResult = DialogResult.OK;
-                form.Close();
-            };
-
-            form.FormClosed += delegate { closed = true; ret(form.DialogResult, textBox.Text); };
-
-            form.Show();
-            form.Height = textBox.Height;
-            textBox.Text = orig;
-            textBox.SelectAll();
-            var tw = TextRenderer.MeasureText(orig, textBox.Font).Width;
-            if (tw > textBox.Width) textBox.Width = tw;
-            form.Width = textBox.Width;
+            e.Graphics.ResetTransform();
+            e.Graphics.DrawString(strPos, Font, Brushes.Black, mid - strPosSize.Width / 2, 0);
+            e.Graphics.DrawLine(Pens.Red, mid, strPosSize.Height, mid, Height);
         }
 
         private void EditorUserControl_MouseMove(object sender, MouseEventArgs e)
@@ -293,6 +323,16 @@ namespace BeatLyrics.Tool.UserControls
                     ctx.Items.Add("Set Start").Click += delegate { _hoverObj.TimeMs = PlayPos; };
                     ctx.Items.Add("Set End").Click += delegate { _hoverObj.DurationMs = PlayPos - _hoverObj.TimeMs; };
                     ctx.Items.Add(new ToolStripSeparator());
+                    ctx.Items.Add("Set Start Follow All").Click += delegate
+                    {
+                        var moveMs = _hoverObj.TimeMs - PlayPos;
+                        var toMove = Items.Where(p => p.TimeMs >= _hoverObj.TimeMs).ToArray();
+                        foreach (var item in toMove)
+                        {
+                            item.TimeMs -= moveMs;
+                        }
+                    };
+                    ctx.Items.Add(new ToolStripSeparator());
                     if (_hoverObj == GetPlayItem())
                     {
                         ctx.Items.Add("Split...").Click += delegate
@@ -308,7 +348,7 @@ namespace BeatLyrics.Tool.UserControls
                 {
                     ctx.Items.Add("Add").Click += delegate
                     {
-                        ShowTextBox(MousePosition, "new", (result, s) =>
+                        ContextDialog.PopTextBox(MousePosition, "new", (result, s) =>
                         {
                             if (result == DialogResult.OK)
                             {
@@ -366,7 +406,7 @@ namespace BeatLyrics.Tool.UserControls
                 {
                     var localVar = _hoverObj;
 
-                    ShowTextBox(PointToScreen(localVar.Hit.Location), localVar.Text, (result, s) =>
+                    ContextDialog.PopTextBox(PointToScreen(localVar.Hit.Location), localVar.Text, (result, s) =>
                     {
                         if (result == DialogResult.OK)
                         {

+ 86 - 0
BeatLyrics.Tool/Utils/SpectrumAnalyzer.cs

@@ -0,0 +1,86 @@
+using BeatLyrics.Tool.RefLibs;
+using CSCore;
+using CSCore.Codecs.OGG;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Numerics;
+using System.Threading.Tasks;
+
+namespace BeatLyrics.Tool.Utils
+{
+    internal static class SpectrumAnalyzer
+    {
+        public static SaResult[] RunOgg(string filePath, int blockSizeInMs, DSP.Window.Type windowType = DSP.Window.Type.Rectangular)
+        {
+            //extract samples from file
+            using var ms = new MemoryStream(File.ReadAllBytes(filePath));
+            using var ogg = new OggSource(ms);
+            using var sampleSource = ogg.ToMono();
+
+            var samples = new List<double[]>();
+            var buffer = new float[(int)(sampleSource.WaveFormat.SampleRate * (blockSizeInMs / 1000f))];
+
+            //Split by block size
+
+            do
+            {
+                var read = sampleSource.Read(buffer, 0, buffer.Length);
+
+                if (read == 0) break; // end of stream
+                if (read < buffer.Length)
+                {
+                    samples.Add(buffer.Take(read).Select(p => (double)p).ToArray());
+                    break;
+                }
+                samples.Add(buffer.Take(read).Select(p => (double)p).ToArray());
+            } while (true);
+
+            // calc each fft
+            var results = new SaResult[samples.Count];
+
+            Parallel.For(0, samples.Count, (i) =>
+            {
+                //feed samples to fft
+                var timeSeries = samples[i];
+                var N = (uint)timeSeries.Length;
+                double[] wc = DSP.Window.Coefficients(windowType, N);
+
+                double windowScaleFactor = DSP.Window.ScaleFactor.Signal(wc);
+                double[] windowedTimeSeries = DSP.Math.Multiply(timeSeries, wc);
+
+                FFT fft = new FFT();
+
+                var pad = 0u;
+                for (int pow = 1; pow <= 32; pow++)
+                {
+                    var p = (uint)System.Math.Pow(2, pow);
+                    if (p >= N)
+                    {
+                        pad = p - N;
+                        break;
+                    }
+                }
+
+                fft.Initialize(N, pad);
+
+                Complex[] cpxResult = fft.Execute(windowedTimeSeries);
+                double[] mag = DSP.ConvertComplex.ToMagnitude(cpxResult);
+                mag = DSP.Math.Multiply(mag, windowScaleFactor);
+                double[] magLog = DSP.ConvertMagnitude.ToMagnitudeDBV(mag);
+
+                double[] fSpan = fft.FrequencySpan(sampleSource.WaveFormat.SampleRate);
+                var item = new SaResult { FreqAxis = fSpan, Values = magLog };
+                results[i] = item;
+            });
+
+            return results;
+        }
+
+        public class SaResult
+        {
+            public double[] FreqAxis { get; set; }
+            public double[] Values { get; set; }
+        }
+    }
+}

+ 5 - 17
BeatLyrics/Plugin.cs

@@ -74,9 +74,11 @@ namespace BeatLyrics
         private static readonly Quaternion Rot = Quaternion.Euler(new Vector3(0, 0, 0));
         private static readonly Vector3 Pos = new Vector3(0, 1.5f, 15f);
         private static readonly float FontSize = 25;
-        private static readonly Color FontColor = new Color(1.0f, 1.0f, 1.0f, 1f);
+        private static readonly Color32 FontColor = new Color32(255, 255, 255, 64);
         private static readonly float OutlineWidth = 0.4f;
         private static readonly Color32 OutlineColor = new Color32(255, 0, 0, 255);
+        //TODO: OutLine
+        //TODO: Select Font
 
         private readonly AudioTimeSyncController _audioTime;
 
@@ -108,6 +110,7 @@ namespace BeatLyrics
             textObj.transform.localScale = Vector3.one;
 
             _text = textObj.AddComponent<TextMeshProUGUI>();
+
             _text.alignment = TextAlignmentOptions.Center;
             _text.fontSize = FontSize;
             _text.enableWordWrapping = false;
@@ -115,24 +118,9 @@ namespace BeatLyrics
             _text.name = "Lyrics Text";
             _text.text = "BeatLyrics";
 
-            var to = _text.gameObject.AddComponent<TrickObject>();
-            to.text = _text;
-            //TODO: Select Font
             //text.font=
         }
 
-        private class TrickObject : MonoBehaviour
-        {
-            public TextMeshProUGUI text;
-
-            public void LateUpdate()
-            {
-                //TODO: Check here
-                text.fontMaterial.SetColor("_OutlineColor", OutlineColor);
-                text.fontMaterial.SetFloat("_OutlineWidth", OutlineWidth);
-            }
-        }
-
         public IEnumerator Start()
         {
             var sceneSetup = FindObjectOfType<GameplayCoreSceneSetup>();
@@ -143,7 +131,7 @@ namespace BeatLyrics
             {
                 try
                 {
-                    _data.difficultyBeatmap.SetField("_noteJumpMovementSpeed", 15);
+                    _data.difficultyBeatmap.SetField("_noteJumpMovementSpeed", 17);
                 }
                 catch (Exception e)
                 {