├── .github ├── ISSUE_TEMPLATE │ ├── bug.md │ └── feature.md └── workflows │ ├── build-artifacts.yml │ └── upload-release.yml ├── .gitignore ├── CIUtils └── find-executable.sh ├── Directory.Build.props ├── ExtensionInstaller ├── ExtensionInstaller.csproj ├── Program.cs └── ZipFileHelper.cs ├── LICENSE.txt ├── README.md ├── TuneLab.Base ├── Data │ ├── Command.cs │ ├── CompositeCommand.cs │ ├── DataDocument.cs │ ├── DataLinkedList.cs │ ├── DataList.cs │ ├── DataMap.cs │ ├── DataObject.cs │ ├── DataObjectLinkedList.cs │ ├── DataObjectList.cs │ ├── DataObjectMap.cs │ ├── DataProperty.cs │ ├── Head.cs │ ├── ICommand.cs │ ├── IDataLinkedList.cs │ ├── IDataList.cs │ ├── IDataMap.cs │ ├── IDataObject.cs │ ├── IDataObjectLinkedList.cs │ ├── IDataObjectList.cs │ ├── IDataObjectMap.cs │ ├── IReadOnlyDataLinkedList.cs │ ├── IReadOnlyDataList.cs │ ├── IReadOnlyDataMap.cs │ ├── IReadOnlyDataObjectCollection.cs │ ├── IReadOnlyDataObjectLinkedList.cs │ ├── IReadOnlyDataObjectList.cs │ ├── IReadOnlyDataObjectMap.cs │ ├── RedoOnlyCommand.cs │ └── UndoOnlyCommand.cs ├── Event │ ├── Event.cs │ ├── IEvent.cs │ ├── IInvokableEvent.cs │ ├── IMergableEvent.cs │ ├── INotifiableProperty.cs │ ├── IProvider.cs │ ├── ISubscriber.cs │ ├── MergableEvent.cs │ ├── NotifiableProperty.cs │ ├── Owner.cs │ └── Subscribers.cs ├── Properties │ ├── BooleanConfig.cs │ ├── DataPropertyObject.cs │ ├── DataPropertyValue.cs │ ├── EnumConfig.cs │ ├── IDataValueController.cs │ ├── IValueConfig.cs │ ├── IValueController.cs │ ├── IntegerConfig.cs │ ├── ListConfig.cs │ ├── NumberConfig.cs │ ├── ObjectConfig.cs │ ├── PropertyObject.cs │ ├── PropertyPath.cs │ ├── PropertyValue.cs │ └── StringConfig.cs ├── Science │ ├── FastSinc.cs │ ├── HermiteInterpolation.cs │ ├── ITempoCalculatorHelper.cs │ ├── ITempoHelper.cs │ ├── MathUtility.cs │ └── MusicTheory.cs ├── Structures │ ├── CacheList.cs │ ├── EnumerableWrapper.cs │ ├── EnumeratorWrapper.cs │ ├── ILinkedList.cs │ ├── ILinkedNode.cs │ ├── IMutableList.cs │ ├── IReadOnlyKeyWithValue.cs │ ├── IReadOnlyLinkedList.cs │ ├── IReadOnlyMap.cs │ ├── IReadOnlyOrderedMap.cs │ ├── KeyWithValue.cs │ ├── LinkedList.cs │ ├── Map.cs │ ├── OrderedMap.cs │ ├── Point.cs │ ├── RangeF.cs │ ├── ReadOnlyListWrapper.cs │ ├── ReadOnlyMapWrapper.cs │ └── SafeReadOnlyList.cs ├── TuneLab.Base.csproj └── Utils │ ├── DirtyHandler.cs │ ├── DisposableManager.cs │ ├── Extensions.cs │ ├── Log.cs │ └── MergeHandler.cs ├── TuneLab.Extensions.Formats ├── DataInfo │ ├── AudioPartInfo.cs │ ├── AutomationInfo.cs │ ├── MidiPartInfo.cs │ ├── NoteInfo.cs │ ├── PartInfo.cs │ ├── PhonemeInfo.cs │ ├── ProjectInfo.cs │ ├── TempoInfo.cs │ ├── TimeSignatureInfo.cs │ ├── TrackInfo.cs │ ├── VibratoInfo.cs │ └── VoiceInfo.cs ├── ExportFormatAttribute.cs ├── IExportFormat.cs ├── IImportFormat.cs ├── ImportFormatAttribute.cs └── TuneLab.Extensions.Formats.csproj ├── TuneLab.Extensions.Voices ├── AutomationConfig.cs ├── DirtyType.cs ├── IAutomationValueGetter.cs ├── ISynthesisData.cs ├── ISynthesisNote.cs ├── ISynthesisTask.cs ├── IVoiceEngine.cs ├── IVoiceSource.cs ├── SetDirtyAttribute.cs ├── SynthesisResult.cs ├── SynthesisSegment.cs ├── SynthesizedPhoneme.cs ├── TuneLab.Extensions.Voices.csproj ├── VoiceEngineAttribute.cs └── VoiceSourceInfo.cs ├── TuneLab.sln └── TuneLab ├── Animation ├── AnimationColor.cs ├── AnimationController.cs ├── AnimationCurve.cs ├── AnimationManager.cs ├── AnimationPath.cs ├── AnimationProperty.cs ├── AnimationValue.cs ├── IAnimation.cs └── IAnimationController.cs ├── App.axaml ├── App.axaml.cs ├── AppInfo.cs ├── Assets ├── Font │ └── NotoMono-Regular-1.ttf └── GlobalStyle.axaml ├── Audio ├── AudioData.cs ├── AudioEngine.cs ├── AudioGraph.cs ├── AudioInfo.cs ├── AudioPlayer.cs ├── AudioUtils.cs ├── EmptyAudioData.cs ├── IAudioCodec.cs ├── IAudioData.cs ├── IAudioDecoder.cs ├── IAudioEncoder.cs ├── IAudioPlaybackHandler.cs ├── IAudioProcessor.cs ├── IAudioProvider.cs ├── IAudioResampler.cs ├── IAudioSampleProvider.cs ├── IAudioSource.cs ├── IAudioStream.cs ├── IAudioTrack.cs ├── MonoAudioData.cs ├── NAudio │ └── NAudioCodec.cs ├── SDL2 │ ├── SDLGlobal.cs │ ├── SDLPlaybackData.cs │ └── SDLPlaybackHandler.cs ├── StereoAudioData.cs └── Waveform.cs ├── Configs ├── RecentFilesManager.cs ├── Settings.cs └── SettingsFile.cs ├── ConstantDefine.cs ├── Data ├── AnchorGroup.cs ├── AnchorPoint.cs ├── AudioPart.cs ├── Automation.cs ├── IAnchorGroup.cs ├── IAnchorPoint.cs ├── IAudioPart.cs ├── IAutomation.cs ├── IDuration.cs ├── IMeter.cs ├── IMidiPart.cs ├── INote.cs ├── INoteList.cs ├── IPart.cs ├── IPhoneme.cs ├── IPiecewiseCurve.cs ├── IPlayhead.cs ├── IProject.cs ├── IQuantization.cs ├── ISelectable.cs ├── ISynthesisPiece.cs ├── ITempo.cs ├── ITempoManager.cs ├── ITimeSignature.cs ├── ITimeSignatureManager.cs ├── ITimeline.cs ├── ITrack.cs ├── IVoice.cs ├── MeterStatus.cs ├── MidiPart.cs ├── Note.cs ├── NoteClipboard.cs ├── ParameterClipboard.cs ├── Part.cs ├── Phoneme.cs ├── PiecewiseCurve.cs ├── PitchAutomation.cs ├── Project.cs ├── ProjectDocument.cs ├── Quantization.cs ├── SynthesisStatus.cs ├── Tempo.cs ├── TempoManager.cs ├── TimeSignatureManager.cs ├── Track.cs ├── Vibrato.cs ├── VibratoClipboard.cs └── Voice.cs ├── Extensions ├── ExtensionDescription.cs ├── ExtensionManager.cs ├── Formats │ ├── ACEP │ │ └── ACEStudioProject.cs │ ├── FormatsManager.cs │ ├── Midi │ │ └── Midi.cs │ ├── TLP │ │ └── TuneLabProject.cs │ ├── UFData │ │ └── UtaFormatixV1Data.cs │ └── VPR │ │ └── VocaloidVprProject.cs └── Voices │ ├── EmptyVoiceEngine.cs │ ├── EmptyVoiceSynthesisTask.cs │ └── VoicesManager.cs ├── GUI ├── Alignment.cs ├── AnimationScalableScrollAxis.cs ├── Assets.cs ├── ColorSet.cs ├── Components │ ├── AbstractSlider.cs │ ├── AmplitudeViewer.cs │ ├── Button.cs │ ├── CheckBox.cs │ ├── CollapsiblePanel.cs │ ├── Component.cs │ ├── Container.cs │ ├── CurveRenderer.cs │ ├── DropDown.cs │ ├── EditableLabel.cs │ ├── IComponent.cs │ ├── LayerPanel.cs │ ├── ListView.cs │ ├── MovableComponent.cs │ ├── ScrollView.cs │ ├── Slider.cs │ ├── TextInput.cs │ ├── Toggle.cs │ └── View.cs ├── Controllers │ ├── ComboBoxController.cs │ ├── IPropertyController.cs │ ├── ObjectController.cs │ ├── PathInput.cs │ ├── SingleLineTextController.cs │ └── SliderController.cs ├── Dialog.axaml ├── Dialog.axaml.cs ├── IItem.cs ├── IScrollAxis.cs ├── IScrollView.cs ├── Input │ ├── EventArgs.cs │ ├── ModifierKeys.cs │ └── MouseButtonType.cs ├── ScalableScrollAxis.cs ├── Style.cs ├── SvgIcon.cs ├── UpdateDialog.axaml └── UpdateDialog.axaml.cs ├── I18N ├── DummyTranslator.cs ├── ITranslationContext.cs ├── ITranslator.cs ├── TomlTranslator.cs ├── TranslationContext.cs └── TranslationManager.cs ├── PathManager.cs ├── Program.cs ├── Resources └── Translations │ ├── de-DE.toml │ ├── el-GR.toml │ ├── en-US.toml │ ├── es-US.toml │ ├── fr-FR.toml │ ├── it-IT.toml │ ├── ja-JP.toml │ ├── ko-KR.toml │ ├── nl-NL.toml │ ├── pt-BR.toml │ ├── ru-RU.toml │ ├── sv-SE.toml │ ├── tr-TR.toml │ ├── uk-UA.toml │ ├── zh-CN.toml │ └── zh-TW.toml ├── TranslationContexts.cs ├── TuneLab.csproj ├── UI ├── ImportTrackSelector │ ├── ImportTrackSelector.axaml │ └── ImportTrackSelector.axaml.cs ├── LyricInput │ ├── LyricInput.axaml │ └── LyricInput.axaml.cs ├── MainWindow │ ├── Editor │ │ ├── Common │ │ │ ├── PlayheadLayer.cs │ │ │ ├── TickAxis.cs │ │ │ └── TrackColorManager.cs │ │ ├── Editor.cs │ │ ├── FunctionBar │ │ │ ├── AutoPageButton.cs │ │ │ ├── FunctionBar.cs │ │ │ └── PlayScrollTarget.cs │ │ ├── PianoWindow │ │ │ ├── ParameterArea │ │ │ │ ├── AutomationRenderer.cs │ │ │ │ ├── AutomationRendererItem.cs │ │ │ │ ├── AutomationRendererOperation.cs │ │ │ │ ├── ParameterContainer.cs │ │ │ │ └── ParameterTitleBar.cs │ │ │ ├── ParameterTabBar │ │ │ │ ├── ParameterButton.cs │ │ │ │ └── ParameterTabBar.cs │ │ │ ├── PianoRoll │ │ │ │ ├── PianoRoll.cs │ │ │ │ ├── PianoRollItem.cs │ │ │ │ └── PianoRollOperation.cs │ │ │ ├── PianoScrollView │ │ │ │ ├── IPianoScrollView.cs │ │ │ │ ├── PianoScrollView.cs │ │ │ │ ├── PianoScrollViewItem.cs │ │ │ │ └── PianoScrollViewOperation.cs │ │ │ ├── PianoTool.cs │ │ │ ├── PianoWindow.cs │ │ │ └── PitchAxis.cs │ │ ├── SideBar │ │ │ ├── ISideBarContentProvider.cs │ │ │ ├── Properties │ │ │ │ ├── MidiPartFixedController.cs │ │ │ │ └── PropertySideBarContentProvider.cs │ │ │ ├── SideBar.cs │ │ │ ├── SideBarTab.cs │ │ │ └── SideTabBar.cs │ │ ├── TimelineView │ │ │ ├── TimelineView.cs │ │ │ ├── TimelineViewItem.cs │ │ │ └── TimelineViewOperation.cs │ │ └── TrackWindow │ │ │ ├── TrackHeadList │ │ │ ├── GainSlider.cs │ │ │ ├── PanSlider.cs │ │ │ ├── TrackHead.cs │ │ │ └── TrackHeadList.cs │ │ │ ├── TrackScrollView │ │ │ ├── TrackScrollView.cs │ │ │ ├── TrackScrollViewItem.cs │ │ │ └── TrackScrollViewOperation.cs │ │ │ ├── TrackVerticalAxis.cs │ │ │ └── TrackWindow.cs │ ├── MainWindow.axaml │ └── MainWindow.axaml.cs └── Settings │ ├── SettingsWindow.axaml │ └── SettingsWindow.axaml.cs ├── Utils ├── AppUpdateManager.cs ├── Extensions.cs ├── FileLogger.cs ├── HttpClient.cs ├── IDGenerator.cs ├── LockFile.cs ├── LyricUtils.cs ├── ObjectPoolManager.cs ├── PlatformHelper.cs ├── ProcessHelper.cs └── ZipFileHelper.cs └── app.manifest /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 新的bug报告 | Bug Report 3 | about: 用于报告发现的bug | Use this template for reporting bugs. 4 | title: "[12-31]: 在此描述你发现的bug" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### BUG 描述 | Bug Report 11 | _简要描述这个 bug,说明预期表现与实际表现_ 12 | _Briefly describe the bug and explain the expected and actual performance._ 13 | 14 | ### 如何复现 | How to reproduce 15 | 1. _点击 '...'_ 16 | 2. _进入 xx 页面后点击 '....'_ 17 | 3. _滚动到第二屏之后点击 '....'_ 18 | 4. _bug 出现_ 19 | 20 | 21 | ### 更多细节 22 | 标题 | 字段值 23 | :- | - 24 | 系统 | _Window 11_ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 新的功能需求 | Feature Request 3 | about: 新的功能需求 | Use this template for tracking new features. 4 | title: "[12-10]: XXX的功能需求与效果实现" 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 功能描述 11 | _例如:添加PIT参数控制点模式_ -------------------------------------------------------------------------------- /CIUtils/find-executable.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if a directory is provided as an argument 4 | if [ -z "$1" ]; then 5 | echo "Usage: $0 " 6 | exit 1 7 | fi 8 | 9 | # Directory to search 10 | directory=$1 11 | 12 | # Find ELF, Mach-O and Universal Binary files in the specified directory 13 | files=$(find "$directory" -type f -exec sh -c 'file -b "$1" | grep -qE "ELF|Mach-O|universal binary" && echo "$1" | sed "s:/\{1,\}:/:g"' _ {} \;) 14 | 15 | # Initialize an empty string for the output 16 | output="" 17 | 18 | # Loop through each file and format it 19 | for file in $files; do 20 | if [ -z "$output" ]; then 21 | output="\"$file\"" 22 | else 23 | output="$output,\"$file\"" 24 | fi 25 | done 26 | 27 | # Print the formatted output 28 | echo $output 29 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | 11.0.2 5 | 6 | 7 | -------------------------------------------------------------------------------- /ExtensionInstaller/ExtensionInstaller.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | ..\TuneLab\bin 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jingang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TuneLab 2 | TuneLab is a lightweight singing voice synthesis editor that supports multiple synthesis engines and supports importing or exporting multiple project formats. 3 | ## Extension installation 4 | Drag the `tlx` extension package file into the editor to install it. 5 | ## Extension development 6 | You can develop your own project formats and synthesis engine extensions. 7 | - You can place your project in the `/Extensions` folder and .gitignore will automatically ignore everything in that folder. 8 | - Adding a `description.json` file to the extension package allows TuneLab to better support your extension. Its content is as follows: 9 | 10 | |Field Name|Field Type|Required|Description| 11 | |-|-|-|-| 12 | |name|string|√|Your extension name. 13 | |company|string|×|Your company name. 14 | |platforms|string Array|×|Supported platforms, null or empty means all. All available values see `Platforms` 15 | |assemblies|string Array|×|Assemblies containing extension interfaces. If null, TuneLab will try to load all assemblies. 16 | |version|string|×|Your extension version. 17 | 18 | - Platforms 19 | 20 | Platform field consists of \-\ or \ (e.g. "win-x64" "osx"). 21 | - Available OS values: `osx` `win` 22 | - Available Architecture values: `x64` `x86` `arm64` `arm` 23 | 24 | If the value is only \, all architectures of the operating system are considered supported. 25 | 26 | - Pack 27 | 28 | Compress the extension package into a zip and change the suffix to `.tlx`. 29 | 30 | # Translation contributor 31 | | Lang | contributor | 32 | |------|:-----------:| 33 | |en-US|-| 34 | |zh-CN|-| 35 | |zh-TW|@justln1113| 36 | |ja-JP|@sevenc-nanashi| 37 | |ko-KR|@Su-Yong| 38 | |es-US|@AnotherNN| 39 | |pt-BR|@overdramatic| 40 | |fr-FR|@LittleAcrasy| 41 | |nl-NL|@RhelaRazer| 42 | |it-IT|@sykhro| 43 | |el-GR|@A-MAIN| 44 | |ru-RU|@Ksauxion| 45 | |uk-UA|@Ksauxion| 46 | |de-DE|@RedBlackAka| 47 | |sv-SE|@ItzIcoza| 48 | |tr-TR|@kulisfy| 49 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Data; 8 | 9 | public class Command(Action redo, Action undo) : ICommand 10 | { 11 | public void Redo() 12 | { 13 | redo(); 14 | } 15 | 16 | public void Undo() 17 | { 18 | undo(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/CompositeCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Data; 8 | 9 | internal class CompositeCommand : ICommand 10 | { 11 | public CompositeCommand(IReadOnlyList commands) 12 | { 13 | foreach (var command in commands) 14 | { 15 | mCommands.Add(command); 16 | } 17 | } 18 | 19 | public void Redo() 20 | { 21 | for (int i = 0; i < mCommands.Count; i++) 22 | { 23 | mCommands[i].Redo(); 24 | } 25 | } 26 | 27 | public void Undo() 28 | { 29 | for (int i = mCommands.Count - 1; i >= 0; i--) 30 | { 31 | mCommands[i].Undo(); 32 | } 33 | } 34 | 35 | List mCommands = new(); 36 | } 37 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/DataObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | using TuneLab.Base.Event; 8 | 9 | namespace TuneLab.Base.Data; 10 | 11 | public class DataObject : IDataObject.Implementation 12 | { 13 | public DataObject(IDataObject? parent = null) 14 | { 15 | if (parent == null) 16 | return; 17 | 18 | Attach(parent); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/DataProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Numerics; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Utils; 8 | 9 | namespace TuneLab.Base.Data; 10 | 11 | public interface IReadOnlyDataProperty : IReadOnlyDataObject where T : notnull 12 | { 13 | T Value { get; } 14 | } 15 | 16 | public interface IDataProperty : IDataObject, IReadOnlyDataProperty where T : notnull 17 | { 18 | internal new class Wrapper(IDataProperty dataProperty) : IDataObject.Wrapper(dataProperty), IDataProperty 19 | { 20 | public T Value => dataProperty.Value; 21 | } 22 | } 23 | 24 | public abstract class DataProperty(DataObject? parent = null) : DataObject(parent), IDataProperty where T : notnull 25 | { 26 | public static implicit operator T(DataProperty dataProperty) => dataProperty.GetInfo(); 27 | public T Value => GetInfo(); 28 | public abstract T GetInfo(); 29 | protected abstract void SetInfo(T info); 30 | protected virtual void Set(T value) => IDataObject.Set(this, value); 31 | void IDataObject.SetInfo(T info) => SetInfo(info); 32 | void IDataObject.Set(T value) => Set(value); 33 | } 34 | 35 | public class DataStruct(DataObject? parent = null) : DataProperty(parent) where T : struct 36 | { 37 | public override T GetInfo() 38 | { 39 | return mValue; 40 | } 41 | 42 | protected override void SetInfo(T info) 43 | { 44 | mValue = info; 45 | } 46 | 47 | public override string? ToString() 48 | { 49 | return mValue.ToString(); 50 | } 51 | 52 | T mValue = new(); 53 | } 54 | 55 | public class DataString(DataObject? parent = null, string value = "") : DataProperty(parent) 56 | { 57 | public override string GetInfo() 58 | { 59 | return mValue; 60 | } 61 | 62 | protected override void SetInfo(string info) 63 | { 64 | mValue = info; 65 | } 66 | 67 | public override string? ToString() 68 | { 69 | return mValue.ToString(); 70 | } 71 | 72 | string mValue = value; 73 | } 74 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/Head.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | public readonly struct Head(int head) : IEquatable 11 | { 12 | public static bool operator ==(Head left, Head right) 13 | { 14 | return left.Equals(right); 15 | } 16 | 17 | public static bool operator !=(Head left, Head right) 18 | { 19 | return !left.Equals(right); 20 | } 21 | 22 | public bool Equals(Head other) 23 | { 24 | return mHead == other.mHead; 25 | } 26 | 27 | readonly int mHead = head; 28 | } 29 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/ICommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Data; 8 | 9 | public interface ICommand 10 | { 11 | public void Undo(); 12 | public void Redo(); 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IDataLinkedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | internal interface IDataLinkedList : IDataObject>, IReadOnlyDataObject>, IReadOnlyDataLinkedList, ILinkedList where T : class, ILinkedNode 11 | { 12 | new List GetInfo(); 13 | List IReadOnlyDataObject>.GetInfo() => GetInfo(); 14 | IEnumerable IReadOnlyDataObject>.GetInfo() => GetInfo(); 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IDataList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | public interface IDataList : IDataObject>, IReadOnlyDataObject>, IReadOnlyDataList, IMutableList 11 | { 12 | new List GetInfo(); 13 | List IReadOnlyDataObject>.GetInfo() => GetInfo(); 14 | IEnumerable IReadOnlyDataObject>.GetInfo() => GetInfo(); 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IDataMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | public interface IDataMap : IDataObject>, IReadOnlyDataMap, IDictionary, IReadOnlyDictionary where TKey : notnull 11 | { 12 | new Map GetInfo(); 13 | IReadOnlyMap IReadOnlyDataObject>.GetInfo() => GetInfo(); 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IDataObjectLinkedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | internal interface IDataObjectLinkedList : IDataLinkedList, IReadOnlyDataObjectLinkedList where T : class, IDataObject, ILinkedNode 11 | { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IDataObjectList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Data; 8 | 9 | public interface IDataObjectList : IDataList, IReadOnlyDataObjectList where T : IDataObject 10 | { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IDataObjectMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Data; 8 | 9 | internal interface IDataObjectMap : IDataMap, IReadOnlyDataObjectMap where TKey : notnull where TValue : IDataObject 10 | { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IReadOnlyDataLinkedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | using TuneLab.Base.Structures; 8 | 9 | namespace TuneLab.Base.Data; 10 | 11 | public interface IReadOnlyDataLinkedList : IReadOnlyDataObject>, IReadOnlyLinkedList 12 | { 13 | IActionEvent ItemAdded { get; } 14 | IActionEvent ItemRemoved { get; } 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IReadOnlyDataList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | public interface IReadOnlyDataList : IReadOnlyDataObject>, IReadOnlyList 11 | { 12 | IActionEvent ItemAdded { get; } 13 | IActionEvent ItemRemoved { get; } 14 | IActionEvent ItemReplaced { get; } 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IReadOnlyDataMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | using TuneLab.Base.Structures; 8 | 9 | namespace TuneLab.Base.Data; 10 | 11 | public interface IReadOnlyDataMap : IReadOnlyDataObject>, IReadOnlyMap where TKey : notnull 12 | { 13 | IActionEvent ItemAdded { get; } 14 | IActionEvent ItemRemoved { get; } 15 | IActionEvent ItemReplaced { get; } 16 | } 17 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IReadOnlyDataObjectCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | public interface IReadOnlyDataObjectCollection 11 | { 12 | IEvent Any(ISubscriber subscriber); 13 | IEvent Any(Func> selector) => Any(new SelectorSubscriber(selector)); 14 | IEvent Any(Action subscribe, Action unsubscribe) => Any(new ActionSubscriber(subscribe, unsubscribe)); 15 | } 16 | 17 | public static class IReadOnlyDataObjectCollectionExtension 18 | { 19 | public static IEvent Any(this IReadOnlyDataObjectCollection collection, Func> selector) where T : IDataObject 20 | { 21 | return collection.Any(new SelectorSubscriber(selector)); 22 | } 23 | 24 | public static IEvent Any(this IReadOnlyDataObjectCollection collection, Action subscribe, Action unsubscribe) where T : IDataObject 25 | { 26 | return collection.Any(new ActionSubscriber(subscribe, unsubscribe)); 27 | } 28 | } -------------------------------------------------------------------------------- /TuneLab.Base/Data/IReadOnlyDataObjectLinkedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | public interface IReadOnlyDataObjectLinkedList : IReadOnlyDataLinkedList, IReadOnlyDataObjectCollection where T : IDataObject 11 | { 12 | IMergableEvent ListModified { get; } 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IReadOnlyDataObjectList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | public interface IReadOnlyDataObjectList : IReadOnlyDataList, IReadOnlyDataObjectCollection where T : IDataObject 11 | { 12 | IActionEvent ListModified { get; } 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/IReadOnlyDataObjectMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | 8 | namespace TuneLab.Base.Data; 9 | 10 | public interface IReadOnlyDataObjectMap : IReadOnlyDataMap, IReadOnlyDataObjectCollection where TKey : notnull where TValue : IDataObject 11 | { 12 | IActionEvent MapModified { get; } 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/RedoOnlyCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Data; 8 | 9 | public class RedoOnlyCommand(Action redo) : ICommand 10 | { 11 | public void Redo() 12 | { 13 | redo(); 14 | } 15 | 16 | public void Undo() 17 | { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab.Base/Data/UndoOnlyCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Data; 8 | 9 | public class UndoOnlyCommand(Action undo) : ICommand 10 | { 11 | public void Redo() 12 | { 13 | 14 | } 15 | 16 | public void Undo() 17 | { 18 | undo(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab.Base/Event/IEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Utils; 7 | 8 | namespace TuneLab.Base.Event; 9 | 10 | public interface IEvent 11 | { 12 | void Subscribe(TEvent invokable); 13 | void Unsubscribe(TEvent invokable); 14 | } 15 | 16 | public interface IActionEvent : IEvent { } 17 | public interface IActionEvent : IEvent> { } 18 | public interface IActionEvent : IEvent> { } 19 | public interface IActionEvent : IEvent> { } 20 | 21 | public static class IEventExtensions 22 | { 23 | public static void Subscribe(this IEvent e, TEvent invokable, DisposableManager? context = null) 24 | { 25 | e.Subscribe(invokable); 26 | if (context == null) 27 | return; 28 | 29 | context.Add(new Subscription(e, invokable)); 30 | } 31 | 32 | class Subscription(IEvent e, TEvent invokable) : IDisposable 33 | { 34 | public void Dispose() 35 | { 36 | e.Unsubscribe(invokable); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /TuneLab.Base/Event/IInvokableEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Event; 8 | 9 | public interface IInvokableEvent 10 | { 11 | TEvent InvokeEvent { get; } 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab.Base/Event/IMergableEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Event; 8 | 9 | public interface IMergableEvent : IActionEvent 10 | { 11 | void BeginMerge(); 12 | void EndMerge(); 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Event/INotifiableProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Event; 8 | 9 | public interface INotifiableProperty 10 | { 11 | IActionEvent Modified { get; } 12 | IActionEvent WillModify { get; } 13 | T Value { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Base/Event/ISubscriber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Event; 8 | 9 | public interface ISubscriber 10 | { 11 | void Subscribe(T observable, TFunction action); 12 | void Unsubscribe(T observable, TFunction action); 13 | } 14 | 15 | public static class ISubscriberExtension 16 | { 17 | public static void Subscribe(this ISubscriber subscriber, T observable, IInvokableEvent action) 18 | { 19 | subscriber.Subscribe(observable, action.InvokeEvent); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TuneLab.Base/Event/MergableEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Utils; 7 | 8 | namespace TuneLab.Base.Event; 9 | 10 | public class MergableEvent : IMergableEvent 11 | { 12 | public static implicit operator Action(MergableEvent e) => e.Invoke; 13 | 14 | public MergableEvent() 15 | { 16 | mMergeHandler = new(() => { Action?.Invoke(); }); 17 | } 18 | 19 | public void Subscribe(Action action) 20 | { 21 | Action += action; 22 | } 23 | 24 | public void Unsubscribe(Action action) 25 | { 26 | Action -= action; 27 | } 28 | 29 | public void BeginMerge() 30 | { 31 | mMergeHandler.Begin(); 32 | } 33 | 34 | public void EndMerge() 35 | { 36 | mMergeHandler.End(); 37 | } 38 | 39 | public void Invoke() 40 | { 41 | mMergeHandler.Trigger(); 42 | } 43 | 44 | event Action? Action; 45 | 46 | readonly MergeHandler mMergeHandler; 47 | } 48 | -------------------------------------------------------------------------------- /TuneLab.Base/Event/NotifiableProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Event; 8 | 9 | public class NotifiableProperty(T defaultValue) : INotifiableProperty where T : notnull 10 | { 11 | public IActionEvent WillModify => mWillModify; 12 | public IActionEvent Modified => mModified; 13 | 14 | public static implicit operator NotifiableProperty(T value) 15 | { 16 | return new NotifiableProperty(value); 17 | } 18 | 19 | public static implicit operator T(NotifiableProperty property) 20 | { 21 | return property.Value; 22 | } 23 | 24 | public T Value 25 | { 26 | get => mValue; 27 | set 28 | { 29 | if (Equals(value, mValue)) 30 | return; 31 | 32 | mWillModify.Invoke(); 33 | mValue = value; 34 | mModified.Invoke(); 35 | } 36 | } 37 | 38 | T mValue = defaultValue; 39 | 40 | readonly ActionEvent mWillModify = new(); 41 | readonly ActionEvent mModified = new(); 42 | } 43 | -------------------------------------------------------------------------------- /TuneLab.Base/Event/Owner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Base.Event; 8 | using TuneLab.Base.Utils; 9 | 10 | namespace TuneLab.Base.Event; 11 | 12 | public class Owner : IProvider where T : class 13 | { 14 | public IActionEvent ObjectWillChange => mObjectWillChanged; 15 | public IActionEvent ObjectChanged => mObjectChanged; 16 | public T? Object 17 | { 18 | get => mObject; 19 | set => Set(value); 20 | } 21 | 22 | public static implicit operator T?(Owner owner) 23 | { 24 | return owner.Object; 25 | } 26 | 27 | public void Set(T? newDataObject) 28 | { 29 | if (mObject == newDataObject) 30 | return; 31 | 32 | mObjectWillChanged.Invoke(); 33 | mObject = newDataObject; 34 | mObjectChanged.Invoke(); 35 | } 36 | 37 | T? mObject; 38 | readonly ActionEvent mObjectWillChanged = new(); 39 | readonly ActionEvent mObjectChanged = new(); 40 | } 41 | -------------------------------------------------------------------------------- /TuneLab.Base/Event/Subscribers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Event; 8 | 9 | internal class ActionSubscriber(Action subscribe, Action unsubscribe) : ISubscriber 10 | { 11 | public void Subscribe(T observable, TEvent function) 12 | { 13 | subscribe(observable, function); 14 | } 15 | 16 | public void Unsubscribe(T observable, TEvent function) 17 | { 18 | unsubscribe(observable, function); 19 | } 20 | } 21 | 22 | internal class SelectorSubscriber(Func> selector) : ISubscriber 23 | { 24 | public void Subscribe(T observable, TEvent function) 25 | { 26 | selector(observable).Subscribe(function); 27 | } 28 | 29 | public void Unsubscribe(T observable, TEvent function) 30 | { 31 | selector(observable).Unsubscribe(function); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TuneLab.Base/Properties/BooleanConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Properties; 8 | 9 | public class BooleanConfig(bool defaultValue = false) : IValueConfig 10 | { 11 | public bool DefaultValue { get; set; } = defaultValue; 12 | PropertyValue IValueConfig.DefaultValue => PropertyValue.Create(DefaultValue); 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Properties/DataPropertyValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | 8 | namespace TuneLab.Base.Properties; 9 | 10 | public class DataPropertyValue : DataStruct 11 | { 12 | public DataPropertyValue() 13 | { 14 | SetInfo(PropertyValue.Invalid); 15 | } 16 | 17 | public DataPropertyValue(bool value) 18 | { 19 | SetInfo(PropertyValue.Create(value)); 20 | } 21 | 22 | public DataPropertyValue(double value) 23 | { 24 | SetInfo(PropertyValue.Create(value)); 25 | } 26 | 27 | public DataPropertyValue(string value) 28 | { 29 | SetInfo(PropertyValue.Create(value)); 30 | } 31 | 32 | protected override void SetInfo(PropertyValue info) 33 | { 34 | { 35 | if (Value.ToObject(out var propertyObject)) 36 | { 37 | if (propertyObject.Map is IDataObject dataObject) 38 | dataObject.Detach(); 39 | } 40 | } 41 | base.SetInfo(info); 42 | { 43 | if (info.ToObject(out var propertyObject)) 44 | { 45 | if (propertyObject.Map is IDataObject dataObject) 46 | dataObject.Attach(this); 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /TuneLab.Base/Properties/EnumConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Utils; 7 | 8 | 9 | namespace TuneLab.Base.Properties; 10 | 11 | public class EnumConfig(IReadOnlyList options, int defaultIndex = 0) : IValueConfig 12 | { 13 | public int DefaultIndex { get; set; } = defaultIndex; 14 | public IReadOnlyList Options { get; set; } = options; 15 | public string DefaultValue 16 | { 17 | get => (uint)DefaultIndex < Options.Count ? Options[DefaultIndex] : string.Empty; 18 | set { DefaultIndex = Options.IndexOf(value); } 19 | } 20 | PropertyValue IValueConfig.DefaultValue => PropertyValue.Create(DefaultValue); 21 | } 22 | -------------------------------------------------------------------------------- /TuneLab.Base/Properties/IValueConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Properties; 8 | 9 | public interface IPropertyConfig 10 | { 11 | 12 | } 13 | 14 | public interface IValueConfig : IPropertyConfig 15 | { 16 | PropertyValue DefaultValue { get; } 17 | } 18 | 19 | public interface IValueConfig : IValueConfig where T : notnull 20 | { 21 | new T DefaultValue { get; } 22 | } 23 | -------------------------------------------------------------------------------- /TuneLab.Base/Properties/IntegerConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Properties; 8 | 9 | internal class IntegerConfig : IValueConfig 10 | { 11 | public int DefaultValue { get; set; } 12 | public int MinValue { get; set; } 13 | public int MaxValue { get; set; } 14 | PropertyValue IValueConfig.DefaultValue => PropertyValue.Create(DefaultValue); 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab.Base/Properties/ListConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Properties; 8 | 9 | internal class ListConfig 10 | { 11 | public IPropertyConfig Element { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab.Base/Properties/NumberConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Properties; 8 | 9 | public class NumberConfig(double defaultValue = 0, double minValue = double.NegativeInfinity, double maxValue = double.PositiveInfinity, bool isInterger = false) : IValueConfig 10 | { 11 | public double DefaultValue { get; set; } = defaultValue; 12 | public double MinValue { get; set; } = minValue; 13 | public double MaxValue { get; set; } = maxValue; 14 | public bool IsInterger { get; set; } = isInterger; 15 | PropertyValue IValueConfig.DefaultValue => PropertyValue.Create(DefaultValue); 16 | } 17 | -------------------------------------------------------------------------------- /TuneLab.Base/Properties/ObjectConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Base.Properties; 9 | 10 | public class ObjectConfig(IReadOnlyOrderedMap properties) : IPropertyConfig 11 | { 12 | public IReadOnlyOrderedMap Properties { get; private set; } = properties; 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Properties/PropertyObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | using TuneLab.Base.Science; 8 | using TuneLab.Base.Utils; 9 | 10 | namespace TuneLab.Base.Properties; 11 | 12 | public sealed class PropertyObject(IReadOnlyMap map) 13 | { 14 | public readonly static PropertyObject Empty = new(Map.Empty); 15 | 16 | public IReadOnlyMap Map => map; 17 | 18 | public T GetValue(string key, T defaultValue) where T : notnull 19 | { 20 | if (map == null) 21 | return defaultValue; 22 | 23 | if (!map.TryGetValue(key, out var value)) 24 | return defaultValue; 25 | 26 | if (!value.To(out var result)) 27 | return defaultValue; 28 | 29 | return result; 30 | } 31 | 32 | public PropertyObject GetObject(string key) 33 | { 34 | return GetValue(key, Empty); 35 | } 36 | 37 | public PropertyObject GetObject(string key, PropertyObject defaultValue) 38 | { 39 | return GetValue(key, defaultValue); 40 | } 41 | 42 | public bool GetBool(string key, bool defaultValue = false) 43 | { 44 | return GetValue(key, defaultValue); 45 | } 46 | 47 | public double GetDouble(string key, double defaultValue = 0) 48 | { 49 | return GetValue(key, defaultValue); 50 | } 51 | 52 | public string GetString(string key, string defaultValue = "") 53 | { 54 | return GetValue(key, defaultValue); 55 | } 56 | 57 | public int GetInt(string key, int defaultValue = 0) 58 | { 59 | return GetDouble(key, defaultValue).Round(); 60 | } 61 | 62 | public T GetEnum(string key, T defaultValue = default) where T : struct, Enum 63 | { 64 | return GetValue(key, string.Empty).ToEnum(defaultValue); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /TuneLab.Base/Properties/StringConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Properties; 8 | 9 | public class StringConfig(string defaultValue = "") : IValueConfig 10 | { 11 | public string DefaultValue { get; set; } = defaultValue; 12 | PropertyValue IValueConfig.DefaultValue => PropertyValue.Create(DefaultValue); 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Science/FastSinc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Base.Science; 9 | 10 | public class FastSinc 11 | { 12 | public FastSinc(int sincSamples, int sincResolution = 512) 13 | { 14 | // init sinc 15 | mResolution = sincResolution; // 对速度几乎无影响 16 | int sincSingleSideLength = sincSamples * sincResolution; 17 | int sincCount = sincSingleSideLength + 1; 18 | mValues = new double[sincCount]; 19 | var win = MathUtility.KaiserWin(sincSingleSideLength * 2, 4.8); // 加窗效果要好一些 20 | mValues[0] = 1; 21 | for (int i = 1; i < sincCount; i++) 22 | { 23 | mValues[i] = MathUtility.Sinc((double)i / sincResolution * Math.PI) * win[sincSingleSideLength - i]; 24 | } 25 | } 26 | 27 | public double Calculate(double x) 28 | { 29 | return mValues[(int)(Math.Abs(x) * mResolution)]; 30 | // 使用线性插值精度要好一些,但要慢1/4左右 31 | double index = Math.Abs(x) * mResolution; 32 | int i = (int)index; 33 | return MathUtility.Lerp(mValues[i], mValues[i + 1], index - i); 34 | } 35 | 36 | readonly double[] mValues; 37 | readonly int mResolution; 38 | } 39 | -------------------------------------------------------------------------------- /TuneLab.Base/Science/ITempoHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Science; 8 | 9 | public interface ITempoHelper 10 | { 11 | public double Pos { get; } 12 | public double Bpm { get; } 13 | public double Time { get; } 14 | public double Coe { get; } 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/EnumerableWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Base.Structures; 9 | 10 | internal class EnumerableWrapper(IEnumerable enumerable, Func convert) : IEnumerable 11 | { 12 | public IEnumerator GetEnumerator() => new EnumeratorWrapper(enumerable.GetEnumerator(), convert); 13 | 14 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 15 | } 16 | 17 | internal class EnumerableWrapper(IEnumerable enumerable, Func convert) : EnumerableWrapper(enumerable, convert) { } 18 | 19 | public static class EnumerableWrapperExtension 20 | { 21 | public static IEnumerable Convert(this IEnumerable enumerable, Func convert) 22 | { 23 | return new EnumerableWrapper(enumerable, convert); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/EnumeratorWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Base.Structures; 9 | 10 | internal struct EnumeratorWrapper(IEnumerator enumerator, Func convert) : IEnumerator 11 | { 12 | public T Current => convert(enumerator.Current); 13 | object? IEnumerator.Current => Current; 14 | 15 | public void Dispose() 16 | { 17 | enumerator.Dispose(); 18 | } 19 | 20 | public bool MoveNext() 21 | { 22 | return enumerator.MoveNext(); 23 | } 24 | 25 | public void Reset() 26 | { 27 | enumerator.Reset(); 28 | } 29 | } 30 | 31 | public static class EnumeratorWrapperExtension 32 | { 33 | public static IEnumerator Convert(this IEnumerator enumerator, Func convert) 34 | { 35 | return new EnumeratorWrapper(enumerator, convert); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/ILinkedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Structures; 8 | 9 | public interface ILinkedList : IReadOnlyLinkedList where T : class, ILinkedNode 10 | { 11 | void Insert(T item); 12 | bool Remove(T item); 13 | void InsertAfter(T last, T item); 14 | void InsertBefore(T next, T item); 15 | void Clear(); 16 | bool Contains(T item); 17 | } 18 | 19 | public static class ILinkedListExtension 20 | { 21 | public static void AddBegin(this ILinkedList linkedList, T item) where T : class, ILinkedNode 22 | { 23 | if (linkedList.Begin == null) 24 | { 25 | linkedList.Insert(item); 26 | } 27 | else 28 | { 29 | linkedList.InsertBefore(linkedList.Begin, item); 30 | } 31 | } 32 | 33 | public static void AddEnd(this ILinkedList linkedList, T item) where T : class, ILinkedNode 34 | { 35 | if (linkedList.End == null) 36 | { 37 | linkedList.Insert(item); 38 | } 39 | else 40 | { 41 | linkedList.InsertAfter(linkedList.End, item); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/ILinkedNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Structures; 8 | 9 | public interface ILinkedNode where T : class, ILinkedNode 10 | { 11 | T? Next { get; set; } 12 | T? Last { get; set; } 13 | ILinkedList? LinkedList { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/IMutableList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Structures; 8 | 9 | public interface IMutableList : IList, IReadOnlyList 10 | { 11 | new int Count { get; } 12 | new T this[int index] { get; set; } 13 | 14 | int ICollection.Count => Count; 15 | T IList.this[int index] { get => this[index]; set => this[index] = value; } 16 | 17 | int IReadOnlyCollection.Count => Count; 18 | T IReadOnlyList.this[int index] => this[index]; 19 | } 20 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/IReadOnlyKeyWithValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Structures; 8 | 9 | public interface IReadOnlyKeyWithValue 10 | { 11 | TKey Key { get; } 12 | TValue Value { get; } 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/IReadOnlyLinkedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Structures; 8 | 9 | public interface IReadOnlyLinkedList : IReadOnlyCollection 10 | { 11 | T? Begin { get; } 12 | T? End { get; } 13 | IEnumerator Inverse(); 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/IReadOnlyMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TuneLab.Base.Structures; 10 | 11 | public interface IReadOnlyMap : IEnumerable>, IEnumerable, IReadOnlyCollection> where TKey : notnull 12 | { 13 | TValue this[TKey key] { get; } 14 | IEnumerable Keys { get; } 15 | IEnumerable Values { get; } 16 | bool ContainsKey(TKey key); 17 | TValue? GetValue(TKey key, out bool success); 18 | } 19 | 20 | public static class IReadOnlyMapExtension 21 | { 22 | public static bool TryGetValue(this IReadOnlyMap map, TKey key, [MaybeNullWhen(false)] out TValue value) where TKey : notnull 23 | { 24 | value = map.GetValue(key, out var success); 25 | return success; 26 | } 27 | 28 | public static Map ToMap(this IReadOnlyMap map) where TKey : notnull 29 | { 30 | Map newMap = new(); 31 | foreach (var kvp in map) 32 | { 33 | newMap.Add(kvp.Key, kvp.Value); 34 | } 35 | return newMap; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/IReadOnlyOrderedMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Structures; 8 | 9 | public interface IReadOnlyOrderedMap : IReadOnlyMap, IReadOnlyList> where TKey : notnull 10 | { 11 | TKey KeyAt(int index); 12 | TValue ValueAt(int index); 13 | } 14 | 15 | internal static class IReadOnlyOrderedMapExtension 16 | { 17 | public static IReadOnlyKeyWithValue At(this IReadOnlyOrderedMap map, int index) where TKey : notnull 18 | { 19 | return map.At(index); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/KeyWithValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Structures; 8 | 9 | public class KeyWithValue(TKey key, TValue value) : IReadOnlyKeyWithValue 10 | { 11 | public TKey Key { get; set; } = key; 12 | public TValue Value { get; set; } = value; 13 | 14 | public KeyWithValue(KeyValuePair pair) : this(pair.Key, pair.Value) { } 15 | 16 | public static implicit operator KeyWithValue(KeyValuePair pair) 17 | { 18 | return new KeyWithValue(pair); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/Map.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Base.Structures; 9 | 10 | public class Map : Dictionary, IReadOnlyMap where TKey : notnull 11 | { 12 | public static IReadOnlyMap Empty = new Map(); 13 | IEnumerable IReadOnlyMap.Keys => Keys; 14 | IEnumerable IReadOnlyMap.Values => Values; 15 | 16 | public TValue? GetValue(TKey key, out bool success) 17 | { 18 | success = TryGetValue(key, out var value); 19 | return value; 20 | } 21 | 22 | IEnumerator> IEnumerable>.GetEnumerator() => 23 | new EnumeratorWrapper, KeyValuePair>(base.GetEnumerator(), t => new KeyWithValue(t)); 24 | } 25 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/Point.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Base.Structures; 9 | 10 | public struct Point(double x, double y) 11 | { 12 | public double X = x; 13 | public double Y = y; 14 | 15 | public static Point operator +(Point p1, Point p2) 16 | { 17 | return new(p1.X + p2.X, p1.Y + p2.Y); 18 | } 19 | public static Point operator -(Point p1, Point p2) 20 | { 21 | return new(p1.X - p2.X, p1.Y - p2.Y); 22 | } 23 | 24 | public static bool operator ==(Point p1, Point p2) 25 | { 26 | return p1.X == p2.X && p1.Y == p2.Y; 27 | } 28 | 29 | public static bool operator !=(Point p1, Point p2) 30 | { 31 | return p1.X != p2.X || p1.Y != p2.Y; 32 | } 33 | 34 | public override string ToString() 35 | { 36 | return string.Format("({0}, {1})", X, Y); 37 | } 38 | 39 | public override bool Equals([NotNullWhen(true)] object? obj) 40 | { 41 | return obj is Point point && point == this; 42 | } 43 | 44 | public override int GetHashCode() 45 | { 46 | unchecked 47 | { 48 | int hash = 17; 49 | hash = (hash * 23) + X.GetHashCode(); 50 | hash = (hash * 23) + Y.GetHashCode(); 51 | return hash; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/RangeF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Structures; 8 | 9 | public struct RangeF 10 | { 11 | public double min; 12 | public double max; 13 | public RangeF(double min, double max) 14 | { 15 | this.min = min; 16 | this.max = max; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TuneLab.Base/Structures/ReadOnlyListWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Base.Structures; 9 | 10 | internal class ReadOnlyListWrapper(IReadOnlyList list, Func convert) : IReadOnlyList 11 | { 12 | public T this[int index] => convert(list[index]); 13 | public int Count => list.Count; 14 | public IEnumerator GetEnumerator() => new EnumeratorWrapper(list.GetEnumerator(), convert); 15 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 16 | } 17 | 18 | internal class ReadOnlyListWrapper(IReadOnlyList list, Func convert) : ReadOnlyListWrapper(list, convert) { } 19 | 20 | public static class ReadOnlyListWrapperExtension 21 | { 22 | public static IReadOnlyList Convert(this IReadOnlyList list, Func convert) 23 | { 24 | return new ReadOnlyListWrapper(list, convert); 25 | } 26 | } -------------------------------------------------------------------------------- /TuneLab.Base/Structures/ReadOnlyMapWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Base.Structures; 9 | 10 | internal class ReadOnlyMapWrapper(IReadOnlyMap map, Func convert) : IReadOnlyMap where TKey : notnull 11 | { 12 | public T this[TKey key] => convert(key, map[key]); 13 | public IEnumerable Keys => map.Keys; 14 | public IEnumerable Values => new EnumerableWrapper>(map, kvp => convert(kvp.Key, kvp.Value)); 15 | public int Count => map.Count; 16 | public bool ContainsKey(TKey key) => map.ContainsKey(key); 17 | public IEnumerator> GetEnumerator() => new EnumeratorWrapper, IReadOnlyKeyWithValue>(map.GetEnumerator(), kvp => new KeyWithValue(kvp.Key, convert(kvp.Key, kvp.Value))); 18 | 19 | public T? GetValue(TKey key, out bool success) 20 | { 21 | var value = map.GetValue(key, out success); 22 | return value == null ? default : convert(key, value); 23 | } 24 | 25 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 26 | } 27 | 28 | internal class ReadOnlyMapWrapper(IReadOnlyMap map, Func convert) : ReadOnlyMapWrapper(map, convert) where TKey : notnull { } 29 | 30 | public static class ReadOnlyMapWrapperExtension 31 | { 32 | public static IReadOnlyMap Convert(this IReadOnlyMap map, Func convert) where TKey : notnull 33 | { 34 | return new ReadOnlyMapWrapper(map, convert); 35 | } 36 | } -------------------------------------------------------------------------------- /TuneLab.Base/Structures/SafeReadOnlyList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Base.Structures; 9 | 10 | internal class SafeReadOnlyList(IReadOnlyList list, T defaultValue = default) : IReadOnlyList 11 | { 12 | public T this[int index] => index >= 0 && index < list.Count ? list[index] : defaultValue; 13 | 14 | public int Count => list.Count; 15 | 16 | public IEnumerator GetEnumerator() 17 | { 18 | return list.GetEnumerator(); 19 | } 20 | 21 | IEnumerator IEnumerable.GetEnumerator() 22 | { 23 | return GetEnumerator(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TuneLab.Base/TuneLab.Base.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /TuneLab.Base/Utils/DirtyHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Utils; 8 | 9 | public class DirtyHandler 10 | { 11 | public event Action? OnDirty; 12 | public event Action? OnReset; 13 | public bool IsDirty { get; private set; } 14 | public void SetDirty() 15 | { 16 | if (IsDirty) 17 | return; 18 | 19 | IsDirty = true; 20 | OnDirty?.Invoke(); 21 | } 22 | 23 | public void Reset() 24 | { 25 | if (!IsDirty) 26 | return; 27 | 28 | OnReset?.Invoke(); 29 | IsDirty = false; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TuneLab.Base/Utils/DisposableManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Utils; 8 | 9 | public class DisposableManager 10 | { 11 | public static DisposableManager operator +(DisposableManager manager, IDisposable disposable) 12 | { 13 | manager.Add(disposable); 14 | return manager; 15 | } 16 | 17 | public static DisposableManager operator -(DisposableManager manager, IDisposable disposable) 18 | { 19 | manager.Remove(disposable); 20 | return manager; 21 | } 22 | 23 | public void Add(IDisposable disposable) 24 | { 25 | mDisposables.Add(disposable); 26 | } 27 | 28 | public void Remove(IDisposable disposable) 29 | { 30 | mDisposables.Remove(disposable); 31 | } 32 | 33 | public void DisposeAll() 34 | { 35 | foreach (var disposable in mDisposables) 36 | { 37 | disposable.Dispose(); 38 | 39 | } 40 | mDisposables.Clear(); 41 | } 42 | 43 | readonly HashSet mDisposables = new(); 44 | } 45 | -------------------------------------------------------------------------------- /TuneLab.Base/Utils/Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Utils; 8 | 9 | public interface ILogger 10 | { 11 | void WriteLine(string message); 12 | } 13 | 14 | public static class Log 15 | { 16 | public static void SetupLogger(ILogger logger) 17 | { 18 | mLogger = logger; 19 | } 20 | 21 | public static void Debug(object? value) 22 | { 23 | Write("Debug", value); 24 | } 25 | 26 | public static void Info(object? value) 27 | { 28 | Write("Info ", value); 29 | } 30 | 31 | public static void Warning(object? value) 32 | { 33 | Write("Warn ", value); 34 | } 35 | 36 | public static void Error(object? value) 37 | { 38 | Write("Error ", value); 39 | } 40 | 41 | static void Write(string type, object? value) 42 | { 43 | lock (mLock) 44 | { 45 | mLogger?.WriteLine(string.Format("[{0}][{1}][TID:{2}] {3}", type, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), Environment.CurrentManagedThreadId, value)); 46 | } 47 | } 48 | 49 | static readonly object mLock = new(); 50 | 51 | static ILogger? mLogger; 52 | } 53 | -------------------------------------------------------------------------------- /TuneLab.Base/Utils/MergeHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Base.Utils; 8 | 9 | public class MergeHandler(Action action) 10 | { 11 | public static implicit operator Action(MergeHandler mergeHandler) 12 | { 13 | return mergeHandler.Trigger; 14 | } 15 | 16 | public bool IsMerging => mMergingFlag > 0; 17 | 18 | public void Begin() 19 | { 20 | mMergingFlag++; 21 | } 22 | 23 | public void End() 24 | { 25 | if (!IsMerging) 26 | throw new Exception("End Merge without Begin!"); 27 | 28 | mMergingFlag--; 29 | 30 | if (IsMerging) 31 | return; 32 | 33 | if (mRequestCount == 0) 34 | return; 35 | 36 | action(); 37 | mRequestCount = 0; 38 | } 39 | 40 | public void Trigger() 41 | { 42 | if (IsMerging) 43 | { 44 | mRequestCount++; 45 | return; 46 | } 47 | 48 | action(); 49 | } 50 | 51 | int mMergingFlag = 0; 52 | int mRequestCount = 0; 53 | } 54 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/AudioPartInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Formats.DataInfo; 8 | 9 | public class AudioPartInfo : PartInfo 10 | { 11 | public string Path { get; set; } = string.Empty; 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/AutomationInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Extensions.Formats.DataInfo; 9 | 10 | public class AutomationInfo 11 | { 12 | public double DefaultValue { get; set; } 13 | public List Points { get; set; } = new(); 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/MidiPartInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Properties; 7 | using TuneLab.Base.Structures; 8 | 9 | namespace TuneLab.Extensions.Formats.DataInfo; 10 | 11 | public class MidiPartInfo : PartInfo 12 | { 13 | public double Gain { get; set; } = 0; 14 | public VoiceInfo Voice { get; set; } = new VoiceInfo(); 15 | public List Notes { get; set; } = new(); 16 | public Map Automations { get; set; } = new(); 17 | public List> Pitch { get; set; } = new(); 18 | public List Vibratos { get; set; } = new(); 19 | public PropertyObject Properties { get; set; } = PropertyObject.Empty; 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/NoteInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Base.Properties; 8 | using TuneLab.Base.Structures; 9 | 10 | namespace TuneLab.Extensions.Formats.DataInfo; 11 | 12 | public class NoteInfo 13 | { 14 | public required double Pos { get; set; } 15 | public required double Dur { get; set; } 16 | public required int Pitch { get; set; } 17 | public string Lyric { get; set; } = string.Empty; 18 | public string Pronunciation { get; set; } = string.Empty; 19 | public PropertyObject Properties { get; set; } = PropertyObject.Empty; 20 | public List Phonemes { get; set; } = new(); 21 | } 22 | 23 | public static class NoteInfoExtension 24 | { 25 | public static double StartPos(this NoteInfo info) 26 | { 27 | return info.Pos; 28 | } 29 | 30 | public static double EndPos(this NoteInfo info) 31 | { 32 | return info.Pos + info.Dur; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/PartInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Extensions.Formats.DataInfo; 9 | 10 | public abstract class PartInfo 11 | { 12 | public string Name { get; set; } = string.Empty; 13 | public double Pos { get; set; } = 0; 14 | public double Dur { get; set; } = 0; 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/PhonemeInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Formats.DataInfo; 8 | 9 | public class PhonemeInfo 10 | { 11 | public double StartTime { get; set; } 12 | public double EndTime { get; set; } 13 | public string Symbol { get; set; } = string.Empty; 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/ProjectInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Formats.DataInfo; 8 | 9 | public class ProjectInfo 10 | { 11 | public List Tempos { get; set; } = new(); 12 | public List TimeSignatures { get; set; } = new(); 13 | public List Tracks { get; set; } = new(); 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/TempoInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Formats.DataInfo; 8 | 9 | public class TempoInfo 10 | { 11 | public double Pos { get; set; } 12 | public double Bpm { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/TimeSignatureInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Formats.DataInfo; 8 | 9 | public class TimeSignatureInfo 10 | { 11 | public int BarIndex { get; set; } 12 | public int Numerator { get; set; } 13 | public int Denominator { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/TrackInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Extensions.Formats.DataInfo; 9 | 10 | public class TrackInfo 11 | { 12 | public string Name { get; set; } = string.Empty; 13 | public double Gain { get; set; } = 0; 14 | public double Pan { get; set; } = 0; 15 | public bool Mute { get; set; } = false; 16 | public bool Solo { get; set; } = false; 17 | public bool AsRefer { get; set; } = true; 18 | public string Color { get; set; } = string.Empty; 19 | public List Parts { get; set; } = new(); 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/VibratoInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Extensions.Formats.DataInfo; 9 | 10 | public class VibratoInfo 11 | { 12 | public double Pos { get; set; } 13 | public double Dur { get; set; } 14 | public double Frequency { get; set; } 15 | public double Phase { get; set; } 16 | public double Amplitude { get; set; } 17 | public double Attack { get; set; } 18 | public double Release { get; set; } 19 | public Map AffectedAutomations { get; set; } = new(); 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/DataInfo/VoiceInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Formats.DataInfo; 8 | 9 | public class VoiceInfo 10 | { 11 | public string Type { get; set; } = string.Empty; 12 | public string ID { get; set; } = string.Empty; 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/ExportFormatAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Formats; 8 | 9 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 10 | public class ExportFormatAttribute : Attribute 11 | { 12 | public string FileExtension { get; private set; } 13 | 14 | public ExportFormatAttribute(string fileExtension) 15 | { 16 | FileExtension = fileExtension; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/IExportFormat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Extensions.Formats.DataInfo; 8 | 9 | namespace TuneLab.Extensions.Formats; 10 | 11 | public interface IExportFormat 12 | { 13 | Stream Serialize(ProjectInfo info); 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/IImportFormat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TuneLab.Extensions.Formats.DataInfo; 9 | 10 | namespace TuneLab.Extensions.Formats; 11 | 12 | public interface IImportFormat 13 | { 14 | ProjectInfo Deserialize(Stream stream); 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/ImportFormatAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Formats; 8 | 9 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 10 | public class ImportFormatAttribute : Attribute 11 | { 12 | public string FileExtension { get; private set; } 13 | 14 | public ImportFormatAttribute(string fileExtension) 15 | { 16 | FileExtension = fileExtension; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Formats/TuneLab.Extensions.Formats.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/AutomationConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Properties; 7 | 8 | namespace TuneLab.Extensions.Voices; 9 | 10 | public class AutomationConfig(string name, double defaultValue, double minValue, double maxValue, string color) : NumberConfig(defaultValue, minValue, maxValue, false) 11 | { 12 | public string Name { get; private set; } = name; 13 | public string Color { get; private set; } = color; 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/DirtyType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Voices; 8 | 9 | public enum DirtyType 10 | { 11 | Segment, 12 | Duration, 13 | Automation 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/IAutomationValueGetter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Voices; 8 | 9 | public interface IAutomationValueGetter 10 | { 11 | double[] GetValue(IReadOnlyList times); 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/ISynthesisData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Properties; 8 | using TuneLab.Base.Structures; 9 | 10 | namespace TuneLab.Extensions.Voices; 11 | 12 | public interface ISynthesisData 13 | { 14 | IEnumerable Notes { get; } 15 | PropertyObject PartProperties { get; } 16 | bool GetAutomation(string automationID, [MaybeNullWhen(false)] [NotNullWhen(true)] out IAutomationValueGetter? automation); 17 | IAutomationValueGetter Pitch { get; } 18 | } 19 | 20 | public static class ISynthesisDataExtension 21 | { 22 | public static double StartTime(this ISynthesisData data) 23 | { 24 | return data.Notes.First().StartTime; 25 | } 26 | 27 | public static double EndTime(this ISynthesisData data) 28 | { 29 | return data.Notes.Last().EndTime; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/ISynthesisNote.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Properties; 7 | 8 | namespace TuneLab.Extensions.Voices; 9 | 10 | public interface ISynthesisNote 11 | { 12 | ISynthesisNote? Next { get; } 13 | ISynthesisNote? Last { get; } 14 | double StartTime { get; } 15 | double EndTime { get; } 16 | int Pitch { get; } 17 | string Lyric { get; } 18 | PropertyObject Properties { get; } 19 | IReadOnlyList Phonemes { get; } 20 | } 21 | 22 | public static class ISynthesisNoteExtension 23 | { 24 | public static double Duration(this ISynthesisNote note) 25 | { 26 | return note.EndTime - note.StartTime; 27 | } 28 | } -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/ISynthesisTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Voices; 8 | 9 | public interface ISynthesisTask 10 | { 11 | event Action? Complete; 12 | event Action? Progress; 13 | event Action? Error; 14 | void Start(); 15 | void Suspend(); 16 | void Resume(); 17 | void Stop(); 18 | void SetDirty(string dirtyType); 19 | } 20 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/IVoiceEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Extensions.Voices; 9 | 10 | public interface IVoiceEngine 11 | { 12 | IReadOnlyOrderedMap VoiceInfos { get; } 13 | bool Init(string enginePath, out string? error); 14 | void Destroy(); 15 | IVoiceSource CreateVoiceSource(string id); 16 | } 17 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/IVoiceSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Properties; 7 | using TuneLab.Base.Structures; 8 | using TuneLab.Base.Utils; 9 | 10 | namespace TuneLab.Extensions.Voices; 11 | 12 | public interface IVoiceSource 13 | { 14 | string Name { get; } 15 | string DefaultLyric { get; } 16 | IReadOnlyOrderedMap AutomationConfigs { get; } 17 | IReadOnlyOrderedMap PartProperties { get; } 18 | IReadOnlyOrderedMap NoteProperties { get; } 19 | IReadOnlyList> Segment(SynthesisSegment segment) where T : ISynthesisNote; 20 | ISynthesisTask CreateSynthesisTask(ISynthesisData data); 21 | } 22 | 23 | public static class IVoiceSourceExtension 24 | { 25 | public static IReadOnlyList> SimpleSegment(this IVoiceSource voiceSource, SynthesisSegment segment, double minNoteSpacing = 0, double maxPieceDuration = double.MaxValue) where T : ISynthesisNote 26 | { 27 | List> segments = new(); 28 | using var it = segment.Notes.GetEnumerator(); 29 | if (!it.MoveNext()) 30 | return segments; 31 | 32 | List currentSegment = new() { it.Current }; 33 | 34 | while (it.MoveNext()) 35 | { 36 | var currentNote = it.Current; 37 | var previousNote = currentSegment.Last(); 38 | 39 | if (currentNote.Duration() > maxPieceDuration) 40 | continue; 41 | 42 | if (currentNote.EndTime - currentSegment.First().StartTime <= maxPieceDuration && currentNote.StartTime - previousNote.EndTime <= minNoteSpacing) 43 | { 44 | currentSegment.Add(currentNote); 45 | continue; 46 | } 47 | 48 | segments.Add(new SynthesisSegment() { Notes = currentSegment }); 49 | currentSegment = new() { currentNote }; 50 | } 51 | 52 | segments.Add(new SynthesisSegment() { Notes = currentSegment }); 53 | 54 | return segments; 55 | } 56 | } -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/SetDirtyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Voices; 8 | 9 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] 10 | public class SetDirtyAttribute(DirtyType dirtyType) : Attribute 11 | { 12 | public DirtyType DirtyType { get; private set; } = dirtyType; 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/SynthesisResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Extensions.Voices; 9 | 10 | public class SynthesisResult( 11 | double startTime, 12 | int samplingRate, 13 | float[] audioData, 14 | IReadOnlyList>? synthesizedPitch = null, 15 | IReadOnlyDictionary? synthesizedPhoneme = null) 16 | { 17 | public readonly double StartTime = startTime; 18 | public readonly int SamplingRate = samplingRate; 19 | public readonly float[] AudioData = audioData; 20 | public readonly IReadOnlyList> SynthesizedPitch = synthesizedPitch ?? EmptyPitch; 21 | public readonly IReadOnlyDictionary SynthesizedPhonemes = synthesizedPhoneme ?? EmptyPhonemes; 22 | 23 | static readonly IReadOnlyList> EmptyPitch = []; 24 | static readonly IReadOnlyDictionary EmptyPhonemes = new Dictionary(); 25 | } 26 | 27 | public static class SynthesisResultExtension 28 | { 29 | public static double AudioStartTime(this SynthesisResult result) 30 | { 31 | return result.StartTime; 32 | } 33 | 34 | public static double AudioDurationTime(this SynthesisResult result) 35 | { 36 | return (double)result.AudioData.Length / result.SamplingRate; 37 | } 38 | 39 | public static double AudioEndTime(this SynthesisResult result) 40 | { 41 | return result.AudioStartTime() + result.AudioDurationTime(); 42 | } 43 | 44 | public static float[] Read(this SynthesisResult result, int offset, int count) 45 | { 46 | float[] data = new float[count]; 47 | int start = Math.Max(offset, 0); 48 | int end = Math.Min(offset + count, result.AudioData.Length); 49 | for (int i = start; i < end; i++) 50 | { 51 | data[i - offset] = result.AudioData[i]; 52 | } 53 | return data; 54 | } 55 | } -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/SynthesisSegment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Properties; 8 | 9 | namespace TuneLab.Extensions.Voices; 10 | 11 | public struct SynthesisSegment where T : ISynthesisNote 12 | { 13 | public PropertyObject PartProperties; 14 | public IReadOnlyCollection Notes; 15 | 16 | public bool EqualsWith(SynthesisSegment other) 17 | { 18 | return Notes.SequenceEqual(other.Notes); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/SynthesizedPhoneme.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Voices; 8 | 9 | public struct SynthesizedPhoneme 10 | { 11 | public string Symbol; 12 | public double StartTime; 13 | public double EndTime; 14 | 15 | public override string ToString() 16 | { 17 | return string.Format("{{0}: [{1}, {2}]}", Symbol, StartTime, EndTime); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/TuneLab.Extensions.Voices.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/VoiceEngineAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Voices; 8 | 9 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 10 | public class VoiceEngineAttribute(string type) : Attribute 11 | { 12 | public string Type { get; private set; } = type; 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab.Extensions.Voices/VoiceSourceInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Extensions.Voices; 8 | 9 | public struct VoiceSourceInfo 10 | { 11 | public string Name { get; set; } 12 | public string Description { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab/Animation/AnimationColor.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Media; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Utils; 8 | 9 | namespace TuneLab.Animation; 10 | 11 | internal class AnimationColor : AnimationProperty 12 | { 13 | protected override Color Lerp(Color t1, Color t2, double ratio) 14 | { 15 | return t1.Lerp(t2, ratio); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TuneLab/Animation/AnimationController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Utils; 8 | 9 | namespace TuneLab.Animation; 10 | 11 | internal class AnimationController : IAnimationController 12 | { 13 | public event Action? ValueChanged; 14 | [MemberNotNullWhen(true, nameof(mPath))] 15 | public bool IsPlaying => mPath != null; 16 | public double Destination => IsPlaying ? mPath.GetValue(mMillisec) : double.NaN; 17 | public AnimationController(AnimationManager? manager = null) 18 | { 19 | manager ??= AnimationManager.SharedManager; 20 | 21 | mAnimationManager = manager; 22 | } 23 | 24 | public void Play(double millisec, IAnimationPath path) 25 | { 26 | if (IsPlaying) 27 | Stop(); 28 | 29 | mMillisec = millisec; 30 | mPath = path; 31 | Value = mPath.GetValue(0); 32 | mAnimationManager.StartAnimation(this); 33 | } 34 | 35 | public void Translate(double distance) 36 | { 37 | if (!IsPlaying) 38 | return; 39 | 40 | mPath.Translate(distance); 41 | } 42 | 43 | public void Stop() 44 | { 45 | if (!IsPlaying) 46 | return; 47 | 48 | mAnimationManager.StopAnimation(this); 49 | mPath = null; 50 | } 51 | 52 | void IAnimation.Update(double millisec) 53 | { 54 | if (!IsPlaying) 55 | return; 56 | 57 | if (millisec > mMillisec) 58 | { 59 | Value = Destination; 60 | Stop(); 61 | return; 62 | } 63 | 64 | Value = mPath.GetValue(millisec); 65 | } 66 | 67 | double Value { set { ValueChanged?.Invoke(value); } } 68 | 69 | double mMillisec; 70 | IAnimationPath? mPath = null; 71 | 72 | readonly AnimationManager mAnimationManager; 73 | } 74 | -------------------------------------------------------------------------------- /TuneLab/Animation/AnimationCurve.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Animation; 8 | 9 | internal interface IAnimationCurve 10 | { 11 | double GetRatio(double timeRatio); 12 | } 13 | 14 | internal class AnimationCurve : IAnimationCurve 15 | { 16 | public static readonly AnimationCurve Linear = new(x => x); 17 | public static readonly AnimationCurve QuadIn = new(x => x * x); 18 | public static readonly AnimationCurve QuadOut = new(x => 1 - (x - 1) * (x - 1)); 19 | public static readonly AnimationCurve CubicIn = new(x => x * x * x); 20 | public static readonly AnimationCurve CubicOut = new(x => 1 + (x - 1) * (x - 1) * (x - 1)); 21 | //public static readonly AnimationCurve CubicInOut; 22 | //public static readonly AnimationCurve BounceOut; 23 | //public static readonly AnimationCurve BounceIn; 24 | //public static readonly AnimationCurve SpringIn; 25 | //public static readonly AnimationCurve SpringOut; 26 | //public static readonly AnimationCurve SinOut; 27 | //public static readonly AnimationCurve SinIn; 28 | public static readonly AnimationCurve SinInOut = new(x => (1 - Math.Cos(x * Math.PI)) * 0.5); 29 | public static AnimationCurve Default => CubicOut; 30 | 31 | public static implicit operator AnimationCurve(Func funcGetRatio) 32 | { 33 | return new AnimationCurve(funcGetRatio); 34 | } 35 | 36 | public AnimationCurve(Func funcGetRatio) 37 | { 38 | mGetRatio = funcGetRatio; 39 | } 40 | 41 | public double GetRatio(double timeRatio) 42 | { 43 | return mGetRatio(timeRatio); 44 | } 45 | 46 | readonly Func mGetRatio; 47 | } 48 | -------------------------------------------------------------------------------- /TuneLab/Animation/AnimationPath.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Animation; 8 | 9 | internal interface IAnimationPath 10 | { 11 | double GetValue(double millisec); 12 | void Translate(double distance); 13 | } 14 | 15 | internal class AnimationPath : IAnimationPath 16 | { 17 | public AnimationPath(Func funcGetValue) 18 | { 19 | mGetValue = funcGetValue; 20 | } 21 | 22 | public double GetValue(double millisec) 23 | { 24 | return mGetValue(millisec) + mOffset; 25 | } 26 | 27 | public void Translate(double distance) 28 | { 29 | mOffset += distance; 30 | } 31 | 32 | readonly Func mGetValue; 33 | double mOffset = 0; 34 | } 35 | -------------------------------------------------------------------------------- /TuneLab/Animation/AnimationProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Animation; 8 | 9 | internal abstract class AnimationProperty where T : struct 10 | { 11 | public event Action? ValueChanged; 12 | 13 | public static implicit operator T(AnimationProperty p) 14 | { 15 | return p.Value; 16 | } 17 | 18 | public T Value { get; set; } = default; 19 | 20 | 21 | public AnimationProperty() 22 | { 23 | mAnimationController.ValueChanged += (value) => { Value = Lerp(mStart, mEnd, value); ValueChanged?.Invoke(); }; 24 | } 25 | 26 | public void SetTo(T to, double millisec, IAnimationCurve? curve = null) 27 | { 28 | mStart = Value; 29 | mEnd = to; 30 | 31 | mAnimationController.SetFromTo(0, 1, millisec, curve); 32 | } 33 | 34 | protected abstract T Lerp(T t1, T t2, double ratio); 35 | 36 | T mStart; 37 | T mEnd; 38 | readonly AnimationController mAnimationController = new(); 39 | } 40 | -------------------------------------------------------------------------------- /TuneLab/Animation/AnimationValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Science; 7 | 8 | namespace TuneLab.Animation; 9 | 10 | internal class AnimationValue : AnimationProperty 11 | { 12 | protected override double Lerp(double t1, double t2, double ratio) 13 | { 14 | return MathUtility.Lerp(t1, t2, ratio); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TuneLab/Animation/IAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Animation; 8 | internal interface IAnimation 9 | { 10 | void Update(double millisec); 11 | } 12 | -------------------------------------------------------------------------------- /TuneLab/Animation/IAnimationController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Science; 7 | 8 | namespace TuneLab.Animation; 9 | 10 | internal interface IAnimationController : IAnimation 11 | { 12 | void Play(double millisec, IAnimationPath path); 13 | void Translate(double distance); 14 | } 15 | 16 | internal static class IAnimationControllerExtension 17 | { 18 | public static void SetFromTo(this IAnimationController animationController, double from, double to, double millisec, IAnimationCurve? curve = null) 19 | { 20 | curve ??= AnimationCurve.Default; 21 | animationController.Play(millisec, millisec <= 0 ? new AnimationPath((t) => to) : new AnimationPath((t) => MathUtility.Lerp(from, to, curve.GetRatio(t / millisec)))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TuneLab/App.axaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | avares://TuneLab/Assets/Fonts#NotoMono 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /TuneLab/AppInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab; 9 | 10 | internal class AppInfo 11 | { 12 | public static Version Version => Assembly.GetEntryAssembly()!.GetName().Version!; 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab/Assets/Font/NotoMono-Regular-1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYunPlayer/TuneLab/e212ec9d953bd00103dd48e3886deb848cd0a893/TuneLab/Assets/Font/NotoMono-Regular-1.ttf -------------------------------------------------------------------------------- /TuneLab/Assets/GlobalStyle.axaml: -------------------------------------------------------------------------------- 1 |  3 | 7 | 8 | 11 | 12 | 17 | 18 | 21 | 22 | 27 | 28 | -------------------------------------------------------------------------------- /TuneLab/Audio/AudioData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal class AudioData(IReadOnlyList data, int channelCount) : IAudioData 10 | { 11 | public int Count => data.Count / channelCount; 12 | 13 | public float GetLeft(int index) 14 | { 15 | return data[index * channelCount]; 16 | } 17 | 18 | public float GetRight(int index) 19 | { 20 | return data[index * channelCount + 1]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TuneLab/Audio/AudioInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal struct AudioInfo 10 | { 11 | public double duration; 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab/Audio/AudioPlayer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Science; 7 | using TuneLab.Base.Utils; 8 | 9 | namespace TuneLab.Audio; 10 | 11 | internal class AudioPlayer 12 | { 13 | public void Play(IAudioData audio) 14 | { 15 | lock (mLockObject) 16 | { 17 | mAudioClips.Add(new(audio)); 18 | } 19 | } 20 | 21 | public void AddData(int count, float[] buffer, int offset) 22 | { 23 | HashSet playedAudioClips = []; 24 | lock (mLockObject) 25 | { 26 | foreach (var audio in mAudioClips) 27 | { 28 | audio.AddData(count, buffer, offset); 29 | if (audio.Played) 30 | playedAudioClips.Add(audio); 31 | } 32 | mAudioClips.Remove(playedAudioClips); 33 | } 34 | } 35 | 36 | class AudioClip(IAudioData audioData) 37 | { 38 | public bool Played => position >= audioData.Count; 39 | public void AddData(int count, float[] buffer, int offset) 40 | { 41 | int endPosition = Math.Min(audioData.Count, position + count); 42 | for (int i = position; i < endPosition; i++) 43 | { 44 | buffer[offset + (i - position) * 2] += audioData.GetLeft(i); 45 | buffer[offset + (i - position) * 2 + 1] += audioData.GetRight(i); 46 | } 47 | position += count; 48 | } 49 | 50 | int position = 0; 51 | } 52 | 53 | readonly HashSet mAudioClips = []; 54 | 55 | readonly object mLockObject = new(); 56 | } 57 | -------------------------------------------------------------------------------- /TuneLab/Audio/AudioUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Audio; 9 | 10 | internal static class AudioUtils 11 | { 12 | public static IEnumerable AllDecodableFormats => mAudioCodec!.AllDecodableFormats; 13 | 14 | public static void Init(IAudioCodec audioCodec) 15 | { 16 | mAudioCodec = audioCodec; 17 | } 18 | 19 | public static bool TryGetAudioInfo(string path, [NotNullWhen(true)] out AudioInfo audioInfo) 20 | { 21 | try 22 | { 23 | audioInfo = mAudioCodec!.GetAudioInfo(path); 24 | return true; 25 | } 26 | catch 27 | { 28 | audioInfo = new(); 29 | return false; 30 | } 31 | } 32 | 33 | public static float[][] Decode(string path, ref int sampleRate) 34 | { 35 | return mAudioCodec!.Decode(path, ref sampleRate); 36 | } 37 | 38 | public static void EncodeToWav(string path, float[] buffer, int sampleRate, int bitPerSample, int channelCount) 39 | { 40 | mAudioCodec!.EncodeToWav(path, buffer, sampleRate, bitPerSample, channelCount); 41 | } 42 | 43 | public static float[] Resample(float[] buffer, int channelCount, int inputSampleRate, int outputSampleRate) 44 | { 45 | return mAudioCodec!.Resample(new AudioProvider(buffer, inputSampleRate, channelCount), outputSampleRate).ToSamples(); 46 | } 47 | 48 | class AudioProvider(float[] data, int sampleRate, int channelCount) : IAudioProvider 49 | { 50 | public int SampleRate => sampleRate; 51 | public int ChannelCount => channelCount; 52 | public int SamplesPerChannel => data.Length / channelCount; 53 | 54 | public void Read(float[] buffer, int offset, int count) 55 | { 56 | for (int i = 0; i < count; i++) 57 | { 58 | buffer[i + offset] = data[i + alreadyReadCount]; 59 | } 60 | 61 | alreadyReadCount += count; 62 | } 63 | 64 | int alreadyReadCount = 0; 65 | } 66 | 67 | static IAudioCodec? mAudioCodec; 68 | } 69 | -------------------------------------------------------------------------------- /TuneLab/Audio/EmptyAudioData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal class EmptyAudioData : IAudioData 10 | { 11 | public int Count => 0; 12 | 13 | public float GetLeft(int index) 14 | { 15 | return 0; 16 | } 17 | 18 | public float GetRight(int index) 19 | { 20 | return 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioCodec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal interface IAudioCodec : IAudioEncoder, IAudioDecoder, IAudioResampler 10 | { 11 | 12 | } 13 | 14 | internal static class IAudioCodecExtension 15 | { 16 | // 传入的采样率若为0,则返回时采样率值为音频本身的采样率; 17 | // 传入的采样率若为非0值,则返回时采样率为传入的采样率 18 | public static float[][] Decode(this IAudioCodec codec, string path, ref int sampleRate) 19 | { 20 | using var stream = codec.Decode(path); 21 | if (sampleRate == 0) 22 | { 23 | sampleRate = stream.SampleRate; 24 | } 25 | IAudioStream resampled = stream; 26 | if (stream.SampleRate != sampleRate) 27 | { 28 | resampled = codec.Resample(stream, sampleRate); 29 | } 30 | 31 | var result = resampled.ToChannelSamples(); 32 | 33 | if (resampled != stream) 34 | resampled.Dispose(); 35 | 36 | return result; 37 | } 38 | } -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal interface IAudioData 10 | { 11 | int Count { get; } 12 | float GetLeft(int index); 13 | float GetRight(int index); 14 | } 15 | 16 | internal static class IAudioDataExtension 17 | { 18 | public static IAudioData GetAudioData(this IAudioData audioData, int offset, int count) 19 | { 20 | return new AudioData(audioData, offset, count); 21 | } 22 | 23 | class AudioData(IAudioData audioData, int offset, int count) : IAudioData 24 | { 25 | public int Count => count; 26 | 27 | public float GetLeft(int index) 28 | { 29 | int i = index + offset; 30 | return i >= 0 && i < audioData.Count ? audioData.GetLeft(i) : 0; 31 | } 32 | 33 | public float GetRight(int index) 34 | { 35 | int i = index + offset; 36 | return i >= 0 && i < audioData.Count ? audioData.GetRight(i) : 0; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioDecoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Audio; 9 | 10 | internal interface IAudioDecoder 11 | { 12 | IEnumerable AllDecodableFormats { get; } 13 | 14 | AudioInfo GetAudioInfo(string path); 15 | IAudioStream Decode(string path); 16 | } 17 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal interface IAudioEncoder 10 | { 11 | void EncodeToWav(string path, float[] buffer, int sampleRate, int bitPerSample, int channelCount); 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioPlaybackHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal interface IAudioPlaybackHandler 10 | { 11 | public static readonly string AutoDeviceName = "(Auto)"; 12 | 13 | event Action? PlayStateChanged; 14 | event Action? ProgressChanged; 15 | 16 | event Action? CurrentDeviceChanged; 17 | event Action? DevicesChanged; 18 | 19 | bool IsPlaying { get; } 20 | 21 | string CurrentDriver { get; set; } 22 | string CurrentDevice { get; set; } 23 | 24 | int BufferSize { get; set; } 25 | int SampleRate { get; set; } 26 | int ChannelCount { get; set; } 27 | 28 | void Init(IAudioSampleProvider audioSampleProvider); 29 | void Destroy(); 30 | 31 | void Start(); 32 | void Stop(); 33 | 34 | IReadOnlyList GetAllDevices(); 35 | IReadOnlyList GetAllDrivers(); 36 | } 37 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal interface IAudioProcessor 10 | { 11 | void ProcessBlock(float[] buffer, int offset, int position, int count); 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal interface IAudioProvider 10 | { 11 | int SampleRate { get; } 12 | int ChannelCount { get; } 13 | int SamplesPerChannel { get; } 14 | 15 | void Read(float[] buffer, int offset, int count); 16 | } 17 | 18 | internal static class IAudioProviderExtension 19 | { 20 | public static float[][] ToChannelSamples(this IAudioProvider provider) 21 | { 22 | int channelCount = provider.ChannelCount; 23 | int samplesPerChannel = provider.SamplesPerChannel; 24 | 25 | float[][] result = new float[channelCount][]; 26 | for (int channelIndex = 0; channelIndex < channelCount; channelIndex++) 27 | { 28 | result[channelIndex] = new float[samplesPerChannel]; 29 | } 30 | 31 | var buffer = channelCount == 1 ? result[0] : new float[samplesPerChannel * channelCount]; 32 | provider.Read(buffer, 0, buffer.Length); 33 | if (channelCount > 1) 34 | { 35 | for (int channelIndex = 0; channelIndex < channelCount; channelIndex++) 36 | { 37 | var data = result[channelIndex]; 38 | for (int i = 0; i < samplesPerChannel; i++) 39 | { 40 | data[i] = buffer[i * channelCount + channelIndex]; 41 | } 42 | } 43 | } 44 | return result; 45 | } 46 | 47 | public static float[] ToSamples(this IAudioProvider provider) 48 | { 49 | float[] result = new float[provider.SamplesPerChannel * provider.ChannelCount]; 50 | provider.Read(result, 0, result.Length); 51 | return result; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioResampler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal interface IAudioResampler 10 | { 11 | IAudioStream Resample(IAudioProvider input, int outputSampleRate); 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioSampleProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal interface IAudioSampleProvider 10 | { 11 | void Read(float[] buffer, int offset, int count); 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal interface IAudioSource 10 | { 11 | double StartTime { get; } 12 | int SampleRate { get; } 13 | int SampleCount { get; } 14 | IAudioData GetAudioData(int offset, int count); 15 | void OnSampleRateChanged(); 16 | } 17 | 18 | internal static class IAudioSourceExtension 19 | { 20 | public static double StartTime(this IAudioSource source) 21 | { 22 | return source.StartTime; 23 | } 24 | 25 | public static double Duration(this IAudioSource source) 26 | { 27 | return source.SampleCount == 0 ? 0 : (double)source.SampleCount / source.SampleRate; 28 | } 29 | 30 | public static double EndTime(this IAudioSource source) 31 | { 32 | return source.StartTime() + source.Duration(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioStream.cs: -------------------------------------------------------------------------------- 1 | using NAudio.Wave; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Audio; 9 | 10 | internal interface IAudioStream : IAudioProvider, IDisposable 11 | { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab/Audio/IAudioTrack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Utils; 7 | 8 | namespace TuneLab.Audio; 9 | 10 | internal interface IAudioTrack 11 | { 12 | bool IsMute { get; } 13 | bool IsSolo { get; } 14 | double Volume { get; } 15 | double Pan { get; } 16 | double EndTime { get; } 17 | IEnumerable AudioSources { get; } 18 | } 19 | -------------------------------------------------------------------------------- /TuneLab/Audio/MonoAudioData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal class MonoAudioData : IAudioData 10 | { 11 | public int Count => mData.Count; 12 | 13 | public MonoAudioData(IReadOnlyList data) 14 | { 15 | mData = data; 16 | } 17 | 18 | public float GetLeft(int index) 19 | { 20 | return mData[index]; 21 | } 22 | 23 | public float GetRight(int index) 24 | { 25 | return mData[index]; 26 | } 27 | 28 | IReadOnlyList mData; 29 | } 30 | -------------------------------------------------------------------------------- /TuneLab/Audio/StereoAudioData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Audio; 8 | 9 | internal class StereoAudioData : IAudioData 10 | { 11 | public int Count => mLeft.Count; 12 | 13 | public StereoAudioData(IReadOnlyList left, IReadOnlyList right) 14 | { 15 | mLeft = left; 16 | mRight = right; 17 | } 18 | 19 | public float GetLeft(int index) 20 | { 21 | return mLeft[index]; 22 | } 23 | 24 | public float GetRight(int index) 25 | { 26 | return mRight[index]; 27 | } 28 | 29 | public float GetData(int index) 30 | { 31 | return (mLeft[index] + mRight[index]) / 2; 32 | } 33 | 34 | IReadOnlyList mLeft; 35 | IReadOnlyList mRight; 36 | } 37 | -------------------------------------------------------------------------------- /TuneLab/Configs/SettingsFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Configs; 8 | 9 | internal class SettingsFile 10 | { 11 | public string Language { get; set; } = string.Empty; 12 | public double MasterGain { get; set; } = 0; 13 | public string BackgroundImagePath { get; set; } = string.Empty; 14 | public double BackgroundImageOpacity { get; set; } = 0.5; 15 | public double ParameterBoundaryExtension { get; set; } = 5; 16 | public string PianoKeySamplesPath { get; set; } = string.Empty; 17 | public int AutoSaveInterval { get; set; } = 10; 18 | public int BufferSize { get; set; } = 1024; 19 | public int SampleRate { get; set; } = 44100; 20 | public string AudioDriver { get; set; } = string.Empty; 21 | public string AudioDevice { get; set; } = string.Empty; 22 | public double TrackHueChangeRate { get; set; } = 0; 23 | } 24 | -------------------------------------------------------------------------------- /TuneLab/ConstantDefine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | using TuneLab.Extensions.Voices; 8 | 9 | namespace TuneLab; 10 | 11 | internal static class ConstantDefine 12 | { 13 | public static readonly string PitchID = "Pitch"; 14 | public static readonly string PitchName = "Pitch"; 15 | public static readonly string PitchColor = "#FFCF40"; 16 | public static readonly string VolumeID = "Volume"; 17 | public static readonly string VibratoEnvelopeID = "VibratoEnvelope"; 18 | public static readonly IReadOnlyOrderedMap PreCommonAutomationConfigs = new OrderedMap() 19 | { 20 | { VolumeID, new AutomationConfig("Volume", 0, -12, +12, "#737CE5") } 21 | }; 22 | public static readonly IReadOnlyOrderedMap PostCommonAutomationConfigs = new OrderedMap() 23 | { 24 | { VibratoEnvelopeID, new AutomationConfig("VibratoEnvelope", 1, 0, 2, "#73DBE5") } 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /TuneLab/Data/AnchorGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Base.Structures; 8 | using TuneLab.Base.Science; 9 | 10 | namespace TuneLab.Data; 11 | 12 | internal class AnchorGroup : DataList, IAnchorGroup 13 | { 14 | public double Start => this.First().Pos; 15 | public double End => this.Last().Pos; 16 | 17 | public AnchorGroup() 18 | { 19 | 20 | } 21 | 22 | public double[] GetValues(IReadOnlyList ticks) 23 | { 24 | return this.Convert(p => p.ToPoint()).MonotonicHermiteInterpolation(ticks); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TuneLab/Data/AnchorPoint.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Input; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Event; 8 | using TuneLab.Base.Structures; 9 | 10 | namespace TuneLab.Data; 11 | 12 | internal class AnchorPoint(double pos, double value) : IAnchorPoint 13 | { 14 | public double Pos { get; } = pos; 15 | public double Value { get; } = value; 16 | 17 | public IActionEvent SelectionChanged => mSelectionChanged; 18 | public bool IsSelected { get => mIsSelected; set { if (mIsSelected == value) return; mIsSelected = value; mSelectionChanged.Invoke(); } } 19 | 20 | public static implicit operator AnchorPoint(Point point) 21 | { 22 | return new(point); 23 | } 24 | 25 | public AnchorPoint(Point point) : this(point.X, point.Y) { } 26 | 27 | readonly ActionEvent mSelectionChanged = new(); 28 | 29 | bool mIsSelected = false; 30 | } 31 | -------------------------------------------------------------------------------- /TuneLab/Data/IAnchorGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Base.Structures; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal interface IAnchorGroup : IDataList 12 | { 13 | double Start { get; } 14 | double End { get; } 15 | double[] GetValues(IReadOnlyList ticks); 16 | } 17 | 18 | internal static class IAnchorGroupExtension 19 | { 20 | public static double GetValue(this IAnchorGroup anchorGroup, double tick) 21 | { 22 | return anchorGroup.GetValues([tick])[0]; 23 | } 24 | 25 | public static List RangeInfo(this IAnchorGroup anchorGroup, double start, double end) 26 | { 27 | List result = new List(); 28 | if (start >= anchorGroup.End || end <= anchorGroup.Start) 29 | return result; 30 | 31 | if (start >= anchorGroup.Start) 32 | { 33 | result.Add(new Point(0, anchorGroup.GetValue(start))); 34 | } 35 | 36 | foreach (var point in anchorGroup) 37 | { 38 | if (point.Pos <= start) 39 | continue; 40 | 41 | if (point.Pos >= end) 42 | break; 43 | 44 | result.Add(new(point.Pos - start, point.Value)); 45 | } 46 | 47 | if (end <= anchorGroup.End) 48 | { 49 | result.Add(new Point(end - start, anchorGroup.GetValue(end))); 50 | } 51 | 52 | return result; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /TuneLab/Data/IAnchorPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices.ComTypes; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Data; 8 | using TuneLab.Base.Structures; 9 | 10 | namespace TuneLab.Data; 11 | 12 | internal interface IAnchorPoint : ISelectable 13 | { 14 | double Pos { get; } 15 | double Value { get; } 16 | } 17 | 18 | internal static class IAnchorPointExtension 19 | { 20 | public static Point ToPoint(this IAnchorPoint anchorPoint) 21 | { 22 | return new(anchorPoint.Pos, anchorPoint.Value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TuneLab/Data/IAudioPart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Audio; 7 | using TuneLab.Base.Data; 8 | using TuneLab.Base.Event; 9 | using TuneLab.Extensions.Formats.DataInfo; 10 | 11 | namespace TuneLab.Data; 12 | 13 | internal enum AudioPartStatus 14 | { 15 | Linked, 16 | Loading, 17 | Unlinked, 18 | } 19 | 20 | internal interface IAudioPart : IPart, IDataObject 21 | { 22 | INotifiableProperty Status { get; } 23 | IActionEvent AudioChanged { get; } 24 | INotifiableProperty BaseDirectory { get; } 25 | IDataProperty Path { get; } 26 | int ChannelCount { get; } 27 | Waveform GetWaveform(int channelIndex); 28 | void Reload(); 29 | } 30 | -------------------------------------------------------------------------------- /TuneLab/Data/IAutomation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Base.Event; 8 | using TuneLab.Base.Structures; 9 | using TuneLab.Extensions.Formats.DataInfo; 10 | 11 | namespace TuneLab.Data; 12 | 13 | internal interface IAutomation : IDataObject 14 | { 15 | IMidiPart Part { get; } 16 | IActionEvent RangeModified { get; } 17 | IDataProperty DefaultValue { get; } 18 | IReadOnlyList Points { get; } 19 | double[] GetValues(IReadOnlyList ticks); 20 | void AddLine(IReadOnlyList points, double extend); 21 | void Clear(double start, double end, double extend); 22 | void AddPoints(IReadOnlyList points); 23 | void DeletePoints(IReadOnlyList points); 24 | } 25 | 26 | internal static class IAutomationExtension 27 | { 28 | public static double GetValue(this IAutomation automation, double tick) 29 | { 30 | return automation.GetValues([tick])[0]; 31 | } 32 | 33 | public static List RangeInfo(this IAutomation automation, double start, double end) 34 | { 35 | var result = new List(); 36 | 37 | result.Add(new Point(0, automation.GetValue(start) - automation.DefaultValue.Value)); 38 | foreach (var point in automation.Points) 39 | { 40 | if (point.Pos <= start) 41 | continue; 42 | 43 | if (point.Pos >= end) 44 | break; 45 | 46 | result.Add(new(point.Pos - start, point.Value)); 47 | } 48 | result.Add(new Point(end - start, automation.GetValue(end) - automation.DefaultValue.Value)); 49 | 50 | return result; 51 | } 52 | 53 | public static void AddLine(this IAutomation automation, IReadOnlyList points, double extend) 54 | { 55 | automation.AddLine(points.Convert(p => new AnchorPoint(p)), extend); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /TuneLab/Data/IDuration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | 8 | namespace TuneLab.Data; 9 | 10 | internal interface IDuration 11 | { 12 | IActionEvent DurationChanged { get; } 13 | double Duration { get; } 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab/Data/IMeter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Science; 7 | 8 | namespace TuneLab.Data; 9 | 10 | internal interface IMeter 11 | { 12 | int Numerator { get; } 13 | int Denominator { get; } 14 | } 15 | 16 | internal static class IMeterExtension 17 | { 18 | public static int TicksPerBeat(this IMeter meter) 19 | { 20 | return MusicTheory.RESOLUTION * 4 / meter.Denominator; 21 | } 22 | 23 | public static int TicksPerBar(this IMeter meter) 24 | { 25 | return meter.TicksPerBeat() * meter.Numerator; 26 | } 27 | } -------------------------------------------------------------------------------- /TuneLab/Data/INoteList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | 8 | namespace TuneLab.Data; 9 | 10 | internal interface INoteList : IReadOnlyDataObjectLinkedList, ISelectableCollection 11 | { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab/Data/IPart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Audio; 7 | using TuneLab.Base.Data; 8 | using TuneLab.Base.Structures; 9 | using TuneLab.Extensions.Formats.DataInfo; 10 | 11 | namespace TuneLab.Data; 12 | 13 | internal interface IPart : IReadOnlyDataObject, ITimeline, IDuration, IAudioSource, ISelectable, ILinkedNode 14 | { 15 | ITrack Track { get; set; } 16 | new IPart? Next { get; } 17 | new IPart? Last { get; } 18 | IDataProperty Name { get; } 19 | IDataProperty Pos { get; } 20 | IDataProperty Dur { get; } 21 | void Activate(); 22 | void Deactivate(); 23 | } 24 | 25 | internal static class IPartExtension 26 | { 27 | public static double StartPos(this IPart part) 28 | { 29 | return part.Pos.Value; 30 | } 31 | 32 | public static double EndPos(this IPart part) 33 | { 34 | return part.Pos.Value + part.Dur.Value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TuneLab/Data/IPhoneme.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Extensions.Formats.DataInfo; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal interface IPhoneme : IDataObject 12 | { 13 | IDataProperty StartTime { get; } 14 | IDataProperty EndTime { get; } 15 | IDataProperty Symbol { get; } 16 | } 17 | -------------------------------------------------------------------------------- /TuneLab/Data/IPlayhead.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | 8 | namespace TuneLab.Data; 9 | 10 | internal interface IPlayhead 11 | { 12 | IActionEvent PosChanged { get; } 13 | double Pos { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab/Data/IProject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Extensions.Formats.DataInfo; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal interface IProject : IDataObject, ITimeline, IDisposable 12 | { 13 | IReadOnlyDataObjectList Tracks { get; } 14 | void AddTrack(TrackInfo info); 15 | void RemoveTrack(ITrack track); 16 | void RemoveTrackAt(int index); 17 | void InsertTrack(int index, ITrack track); 18 | } 19 | 20 | internal static class IProjectExtension 21 | { 22 | public static IEnumerable AllParts(this IProject project) 23 | { 24 | return project.Tracks.SelectMany(track => track.Parts); 25 | } 26 | 27 | public static IEnumerable AllMidiParts(this IProject project) 28 | { 29 | return project.AllParts().OfType(); 30 | } 31 | 32 | public static IEnumerable AllAudioParts(this IProject project) 33 | { 34 | return project.AllParts().OfType(); 35 | } 36 | 37 | public static void DisableAutoPrepare(this IProject project) 38 | { 39 | foreach (var part in project.AllMidiParts()) 40 | { 41 | part.DisableAutoPrepare(); 42 | } 43 | } 44 | 45 | public static void EnableAutoPrepare(this IProject project) 46 | { 47 | foreach (var part in project.AllMidiParts()) 48 | { 49 | part.EnableAutoPrepare(); 50 | } 51 | } 52 | 53 | public static void BeginMergeReSegment(this IProject project) 54 | { 55 | foreach (var part in project.AllMidiParts()) 56 | { 57 | part.BeginMergeReSegment(); 58 | } 59 | } 60 | 61 | public static void EndMergeReSegment(this IProject project) 62 | { 63 | foreach (var part in project.AllMidiParts()) 64 | { 65 | part.EndMergeReSegment(); 66 | } 67 | } 68 | 69 | public static void BeginMergeDirty(this IProject project) 70 | { 71 | foreach (var part in project.AllMidiParts()) 72 | { 73 | part.BeginMergeDirty(); 74 | } 75 | } 76 | 77 | public static void EndMergeDirty(this IProject project) 78 | { 79 | foreach (var part in project.AllMidiParts()) 80 | { 81 | part.EndMergeDirty(); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /TuneLab/Data/IQuantization.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Science; 7 | 8 | namespace TuneLab.Data; 9 | 10 | internal interface IQuantization 11 | { 12 | event Action QuantizationChanged; 13 | MusicTheory.QuantizationBase Base { get; set; } 14 | MusicTheory.QuantizationDivision Division { get; set; } 15 | } 16 | 17 | internal static class IQuantizationExtension 18 | { 19 | public static int Level(this IQuantization quantization) 20 | { 21 | return (int)quantization.Base * (int)quantization.Division; 22 | } 23 | 24 | public static int TicksPerCell(this IQuantization quantization) 25 | { 26 | return MusicTheory.RESOLUTION / quantization.Level(); 27 | } 28 | 29 | public static void Set(this IQuantization quantization, MusicTheory.QuantizationBase quantizationBase, MusicTheory.QuantizationDivision quantizationDivision) 30 | { 31 | quantization.Base = quantizationBase; 32 | quantization.Division = quantizationDivision; 33 | } 34 | 35 | public static void Set(this IQuantization quantization, int level) 36 | { 37 | switch (level) 38 | { 39 | case 1: 40 | quantization.Base = MusicTheory.QuantizationBase.Base_1; 41 | quantization.Division = MusicTheory.QuantizationDivision.Division_1; 42 | break; 43 | case 2: 44 | quantization.Base = MusicTheory.QuantizationBase.Base_1; 45 | quantization.Division = MusicTheory.QuantizationDivision.Division_2; 46 | break; 47 | case 3: 48 | quantization.Base = MusicTheory.QuantizationBase.Base_3; 49 | quantization.Division = MusicTheory.QuantizationDivision.Division_1; 50 | break; 51 | case 4: 52 | quantization.Base = MusicTheory.QuantizationBase.Base_1; 53 | quantization.Division = MusicTheory.QuantizationDivision.Division_4; 54 | break; 55 | case 5: 56 | quantization.Base = MusicTheory.QuantizationBase.Base_5; 57 | quantization.Division = MusicTheory.QuantizationDivision.Division_1; 58 | break; 59 | // TODO 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /TuneLab/Data/ISynthesisPiece.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Audio; 7 | using TuneLab.Extensions.Voices; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal interface ISynthesisPiece : ISynthesisData 12 | { 13 | event Action? Finished; 14 | event Action? Progress; 15 | new IEnumerable Notes { get; } 16 | double SynthesisProgress { get; } 17 | string? LastError { get; } 18 | SynthesisStatus SynthesisStatus { get; } 19 | bool IsSynthesisEnabled { get; } 20 | SynthesisResult? SynthesisResult { get; } 21 | Waveform? Waveform { get; } 22 | void StartSynthesis(); 23 | void SetDirty(string dirtyType); 24 | IEnumerable ISynthesisData.Notes => Notes; 25 | double AudioStartTime { get; } 26 | int SampleRate { get; } 27 | int SampleCount { get; } 28 | } 29 | 30 | internal static class ISynthesisPieceExtension 31 | { 32 | public static double StartTime(this ISynthesisPiece piece) 33 | { 34 | return ((ISynthesisData)piece).StartTime(); 35 | } 36 | 37 | public static double EndTime(this ISynthesisPiece piece) 38 | { 39 | return ((ISynthesisData)piece).EndTime(); 40 | } 41 | 42 | public static double AudioStartTime(this ISynthesisPiece piece) 43 | { 44 | return piece.AudioStartTime; 45 | } 46 | 47 | public static double AudioEndTime(this ISynthesisPiece piece) 48 | { 49 | return piece.AudioStartTime + (piece.SampleCount == 0 ? 0 : (double)piece.SampleCount / piece.SampleRate); 50 | } 51 | } -------------------------------------------------------------------------------- /TuneLab/Data/ITempo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Extensions.Formats.DataInfo; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal interface ITempo : IDataObject 12 | { 13 | double Pos { get; } 14 | double Bpm { get; } 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab/Data/ITempoManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices.ComTypes; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Data; 8 | using TuneLab.Base.Science; 9 | using TuneLab.Utils; 10 | using TuneLab.Extensions.Formats.DataInfo; 11 | using TuneLab.Base.Utils; 12 | 13 | namespace TuneLab.Data; 14 | 15 | internal interface ITempoManager : IDataObject> 16 | { 17 | IProject Project { get; } // TODO: Remove this 18 | IReadOnlyList Tempos { get; } 19 | int AddTempo(double pos, double bpm); 20 | void RemoveTempoAt(int index); 21 | void SetBpm(int index, double bpm); 22 | double[] GetTimes(IReadOnlyList ticks); 23 | double[] GetTicks(IReadOnlyList times); 24 | double GetTick(double time); 25 | double GetTime(double tick); 26 | } 27 | 28 | internal static class ITempoManagerExtension 29 | { 30 | public static void RemoveTempo(this ITempoManager manager, ITempo tempo) 31 | { 32 | manager.RemoveTempoAt(manager.Tempos.IndexOf(tempo)); 33 | } 34 | 35 | public static void SetBpm(this ITempoManager manager, ITempo tempo, double bpm) 36 | { 37 | manager.SetBpm(manager.Tempos.IndexOf(tempo), bpm); 38 | } 39 | 40 | public static double GetBpmAt(this ITempoManager manager, double tick) 41 | { 42 | for (int i = manager.Tempos.Count - 1; i >= 0; i--) 43 | { 44 | var tempo = manager.Tempos[i]; 45 | if (tempo.Pos <= tick) 46 | return tempo.Bpm; 47 | } 48 | 49 | return manager.Tempos[0].Bpm; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /TuneLab/Data/ITimeSignature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Data; 8 | using TuneLab.Extensions.Formats.DataInfo; 9 | 10 | namespace TuneLab.Data; 11 | 12 | internal interface ITimeSignature : IDataObject, IMeter 13 | { 14 | int BarIndex { get; } 15 | int GlobalBeatIndex { get; } 16 | double Pos { get; } 17 | } 18 | 19 | internal static class ITimeSignatureExtension 20 | { 21 | public static double GetTickByBarIndex(this ITimeSignature timeSignature, int barIndex) 22 | { 23 | return timeSignature.Pos + (barIndex - timeSignature.BarIndex) * timeSignature.TicksPerBar(); 24 | } 25 | 26 | public static double GetTickByBarAndBeat(this ITimeSignature timeSignature, int barIndex, int beatIndex) 27 | { 28 | return timeSignature.GetTickByBarIndex(barIndex) + beatIndex * timeSignature.TicksPerBeat(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TuneLab/Data/ITimeline.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Data; 8 | 9 | internal interface ITimeline 10 | { 11 | ITempoManager TempoManager { get; } 12 | ITimeSignatureManager TimeSignatureManager { get; } 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab/Data/ITrack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Audio; 7 | using TuneLab.Base.Data; 8 | using TuneLab.Base.Science; 9 | using TuneLab.Extensions.Formats.DataInfo; 10 | 11 | namespace TuneLab.Data; 12 | 13 | internal interface ITrack : IDataObject, IAudioTrack 14 | { 15 | IProject Project { get; } 16 | ITempoManager TempoManager { get; } 17 | ITimeSignatureManager TimeSignatureManager { get; } 18 | IDataProperty Name { get; } 19 | new IDataProperty IsMute { get; } 20 | new IDataProperty IsSolo { get; } 21 | IDataProperty AsRefer { get; } 22 | IDataProperty Gain { get; } 23 | new IDataProperty Pan { get; } 24 | IDataProperty Color { get; } 25 | IReadOnlyDataObjectLinkedList Parts { get; } 26 | 27 | void InsertPart(IPart part); 28 | bool RemovePart(IPart part); 29 | MidiPart CreatePart(MidiPartInfo info); 30 | AudioPart CreatePart(AudioPartInfo info); 31 | Part CreatePart(PartInfo info); 32 | 33 | void Activate(); 34 | void Deactivate(); 35 | 36 | bool IAudioTrack.IsMute => IsMute.Value; 37 | bool IAudioTrack.IsSolo => IsSolo.Value; 38 | double IAudioTrack.Volume => MusicTheory.dB2Level(Gain.Value); 39 | double IAudioTrack.Pan => Pan.Value; 40 | double IAudioTrack.EndTime => Parts.MaxBy(part => part.EndTime())?.EndTime() ?? 0; 41 | IEnumerable IAudioTrack.AudioSources => Parts; 42 | } 43 | -------------------------------------------------------------------------------- /TuneLab/Data/IVoice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Base.Properties; 8 | using TuneLab.Base.Structures; 9 | using TuneLab.Extensions.Formats.DataInfo; 10 | using TuneLab.Extensions.Voices; 11 | 12 | namespace TuneLab.Data; 13 | 14 | internal interface IVoice : IDataObject 15 | { 16 | string Name { get; } 17 | string DefaultLyric { get; } 18 | IReadOnlyOrderedMap AutomationConfigs { get; } 19 | ObjectConfig PartProperties { get; } 20 | ObjectConfig NoteProperties { get; } 21 | IReadOnlyList> Segment(SynthesisSegment segment) where T : ISynthesisNote; 22 | ISynthesisTask CreateSynthesisTask(ISynthesisData data); 23 | } 24 | -------------------------------------------------------------------------------- /TuneLab/Data/MeterStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Data; 8 | 9 | internal readonly struct MeterStatus 10 | { 11 | public ITimeSignature TimeSignature => mTimeSignatureManager.TimeSignatures[mIndex]; 12 | public int TimeSignatureIndex => mIndex; 13 | public double BarIndex => TimeSignature.BarIndex + (mPos - TimeSignature.Pos) / TimeSignature.TicksPerBar(); 14 | public double GlobalBeatIndex => TimeSignature.GlobalBeatIndex + (mPos - TimeSignature.Pos) / TimeSignature.TicksPerBeat(); 15 | public double BeatIndex => (mPos - TimeSignature.Pos) / TimeSignature.TicksPerBeat() / TimeSignature.Numerator; 16 | public MeterStatus(ITimeSignatureManager manager, int index, double pos) 17 | { 18 | mTimeSignatureManager = manager; 19 | mIndex = index; 20 | mPos = pos; 21 | } 22 | 23 | readonly ITimeSignatureManager mTimeSignatureManager; 24 | readonly int mIndex; 25 | readonly double mPos; 26 | } 27 | -------------------------------------------------------------------------------- /TuneLab/Data/NoteClipboard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Utils; 7 | using TuneLab.Extensions.Formats.DataInfo; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal class NoteClipboard : List 12 | { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab/Data/ParameterClipboard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | using TuneLab.Base.Utils; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal class ParameterClipboard 12 | { 13 | public bool IsEmpty => Pitch.IsEmpty() && Automations.Count == 0; 14 | public required List> Pitch { get; set; } 15 | public required Map> Automations { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /TuneLab/Data/Phoneme.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Extensions.Formats.DataInfo; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal class Phoneme : DataObject, IPhoneme 12 | { 13 | public DataStruct StartTime { get; } = new(); 14 | public DataStruct EndTime { get; } = new(); 15 | public DataString Symbol { get; } = new(); 16 | 17 | IDataProperty IPhoneme.StartTime => StartTime; 18 | IDataProperty IPhoneme.EndTime => EndTime; 19 | IDataProperty IPhoneme.Symbol => Symbol; 20 | 21 | public Phoneme() 22 | { 23 | StartTime.Attach(this); 24 | EndTime.Attach(this); 25 | Symbol.Attach(this); 26 | } 27 | 28 | public PhonemeInfo GetInfo() 29 | { 30 | return new PhonemeInfo() 31 | { 32 | StartTime = StartTime, 33 | EndTime = EndTime, 34 | Symbol = Symbol 35 | }; 36 | } 37 | 38 | public static Phoneme Create(PhonemeInfo info) 39 | { 40 | var phoneme = new Phoneme(); 41 | IDataObject.SetInfo(phoneme, info); 42 | return phoneme; 43 | } 44 | 45 | void IDataObject.SetInfo(PhonemeInfo info) 46 | { 47 | IDataObject.SetInfo(StartTime, info.StartTime); 48 | IDataObject.SetInfo(EndTime, info.EndTime); 49 | IDataObject.SetInfo(Symbol, info.Symbol); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /TuneLab/Data/PitchAutomation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Base.Structures; 8 | using TuneLab.Extensions.Formats.DataInfo; 9 | 10 | namespace TuneLab.Data; 11 | 12 | internal class PitchAutomation : DataObject, IDataObject> 13 | { 14 | public PitchAutomation() 15 | { 16 | 17 | } 18 | 19 | public void AddLine(IReadOnlyList points) 20 | { 21 | 22 | } 23 | 24 | public IReadOnlyList GetInfo() 25 | { 26 | throw new NotImplementedException(); 27 | } 28 | 29 | void IDataObject>.SetInfo(IReadOnlyList info) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TuneLab/Data/Quantization.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Science; 7 | 8 | namespace TuneLab.Data; 9 | 10 | internal class Quantization : IQuantization 11 | { 12 | public event Action? QuantizationChanged; 13 | public MusicTheory.QuantizationBase Base { get => mBase; set { mBase = value; QuantizationChanged?.Invoke(); } } 14 | public MusicTheory.QuantizationDivision Division { get => mDivision; set { mDivision = value; QuantizationChanged?.Invoke(); } } 15 | 16 | public Quantization(MusicTheory.QuantizationBase quantizationBase, MusicTheory.QuantizationDivision quantizationDivision) 17 | { 18 | Base = quantizationBase; 19 | Division = quantizationDivision; 20 | } 21 | 22 | MusicTheory.QuantizationBase mBase; 23 | MusicTheory.QuantizationDivision mDivision; 24 | } 25 | -------------------------------------------------------------------------------- /TuneLab/Data/SynthesisStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Data; 8 | 9 | internal enum SynthesisStatus 10 | { 11 | NotSynthesized, 12 | Synthesizing, 13 | SynthesisFailed, 14 | SynthesisSucceeded 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab/Data/Tempo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Data; 7 | using TuneLab.Extensions.Formats.DataInfo; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal abstract class Tempo(DataObject parent) : DataObject(parent), IDataObject 12 | { 13 | public abstract DataProperty Pos { get; } 14 | public abstract DataProperty Bpm { get; } 15 | 16 | public TempoInfo GetInfo() => new() 17 | { 18 | Pos = Pos, 19 | Bpm = Bpm 20 | }; 21 | 22 | void IDataObject.SetInfo(TempoInfo info) 23 | { 24 | IDataObject.SetInfo(Pos, info.Pos); 25 | IDataObject.SetInfo(Bpm, info.Bpm); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TuneLab/Data/VibratoClipboard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Utils; 7 | using TuneLab.Extensions.Formats.DataInfo; 8 | 9 | namespace TuneLab.Data; 10 | 11 | internal class VibratoClipboard : List 12 | { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab/Data/Voice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Data; 8 | using TuneLab.Base.Properties; 9 | using TuneLab.Base.Structures; 10 | using TuneLab.Extensions.Formats.DataInfo; 11 | using TuneLab.Extensions.Voices; 12 | 13 | namespace TuneLab.Data; 14 | 15 | internal class Voice : DataObject, IVoice 16 | { 17 | public string Name => mVoiceSource.Name; 18 | public string DefaultLyric => mVoiceSource.DefaultLyric; 19 | public IReadOnlyOrderedMap AutomationConfigs => mAutomationConfigs; 20 | public ObjectConfig PartProperties => new(mVoiceSource.PartProperties); 21 | public ObjectConfig NoteProperties => new(mVoiceSource.NoteProperties); 22 | 23 | public Voice(DataObject parent, VoiceInfo info) : base(parent) 24 | { 25 | IDataObject.SetInfo(this, info); 26 | } 27 | 28 | public VoiceInfo GetInfo() 29 | { 30 | return new VoiceInfo() 31 | { 32 | Type = mType, 33 | ID = mID 34 | }; 35 | } 36 | 37 | [MemberNotNull(nameof(mType))] 38 | [MemberNotNull(nameof(mID))] 39 | [MemberNotNull(nameof(mVoiceSource))] 40 | void IDataObject.SetInfo(VoiceInfo info) 41 | { 42 | mType = info.Type; 43 | mID = info.ID; 44 | mVoiceSource = VoicesManager.Create(info.Type, info.ID); 45 | mAutomationConfigs.Clear(); 46 | foreach (var kvp in ConstantDefine.PreCommonAutomationConfigs.Concat(mVoiceSource.AutomationConfigs).Concat(ConstantDefine.PostCommonAutomationConfigs)) 47 | { 48 | mAutomationConfigs.Add(kvp.Key, kvp.Value); 49 | } 50 | } 51 | 52 | public IReadOnlyList> Segment(SynthesisSegment segment) where T : ISynthesisNote 53 | { 54 | return mVoiceSource.Segment(segment); 55 | } 56 | 57 | public ISynthesisTask CreateSynthesisTask(ISynthesisData data) 58 | { 59 | return mVoiceSource.CreateSynthesisTask(data); 60 | } 61 | 62 | string mType; 63 | string mID; 64 | 65 | IVoiceSource mVoiceSource; 66 | OrderedMap mAutomationConfigs = new(); 67 | } 68 | -------------------------------------------------------------------------------- /TuneLab/Extensions/ExtensionDescription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Utils; 8 | using TuneLab.Utils; 9 | 10 | namespace TuneLab.Extensions; 11 | 12 | internal class ExtensionDescription 13 | { 14 | public required string name { get; set; } 15 | public string version { get; set; } = "1.0.0"; 16 | public string[] assemblies { get; set; } = []; 17 | public string[] platforms { get; set; } = []; 18 | 19 | public bool IsPlatformAvailable() 20 | { 21 | if (platforms.IsEmpty()) 22 | return true; 23 | 24 | return platforms.Contains(PlatformHelper.GetOS()) | platforms.Contains(PlatformHelper.GetPlatform()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TuneLab/Extensions/ExtensionManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TuneLab.Extensions.Formats; 9 | using TuneLab.Extensions.Voices; 10 | 11 | namespace TuneLab.Extensions; 12 | 13 | internal static class ExtensionManager 14 | { 15 | public static void LoadExtensions() 16 | { 17 | PathManager.MakeSureExist(PathManager.ExtensionsFolder); 18 | FormatsManager.LoadBuiltIn(); 19 | foreach (var dir in Directory.GetDirectories(PathManager.ExtensionsFolder)) 20 | { 21 | Load(dir); 22 | } 23 | VoicesManager.LoadBuiltIn(); 24 | } 25 | 26 | public static void Destroy() 27 | { 28 | VoicesManager.Destroy(); 29 | } 30 | 31 | public static void Load(string path) 32 | { 33 | FormatsManager.Load(path); 34 | VoicesManager.Load(path); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TuneLab/Extensions/Voices/EmptyVoiceEngine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using TuneLab.Base.Properties; 3 | using TuneLab.Base.Structures; 4 | using TuneLab.Extensions.Formats.DataInfo; 5 | 6 | namespace TuneLab.Extensions.Voices; 7 | 8 | [VoiceEngine("")] 9 | internal class EmptyVoiceEngine : IVoiceEngine 10 | { 11 | public IReadOnlyOrderedMap VoiceInfos => new OrderedMap() { { string.Empty, mVoiceSourceInfo } }; 12 | 13 | public IVoiceSource CreateVoiceSource(string id) 14 | { 15 | return new EmptyVoiceSource(id); 16 | } 17 | 18 | public void Destroy() 19 | { 20 | mAutomationConfigs.Clear(); 21 | } 22 | 23 | public bool Init(string enginePath, out string? error) 24 | { 25 | error = null; 26 | return true; 27 | } 28 | 29 | class EmptyVoiceSource : IVoiceSource 30 | { 31 | public string Name => string.IsNullOrEmpty(mID) ? mVoiceSourceInfo.Name : mID; 32 | 33 | public IReadOnlyOrderedMap AutomationConfigs => mAutomationConfigs; 34 | public IReadOnlyOrderedMap PartProperties => mPartProperties; 35 | public IReadOnlyOrderedMap NoteProperties => mNoteProperties; 36 | 37 | public string DefaultLyric { get; } = "a"; 38 | 39 | public EmptyVoiceSource(string id) 40 | { 41 | mID = id; 42 | } 43 | 44 | public IReadOnlyList> Segment(SynthesisSegment segment) where T : ISynthesisNote 45 | { 46 | return this.SimpleSegment(segment); 47 | } 48 | 49 | public ISynthesisTask CreateSynthesisTask(ISynthesisData data) 50 | { 51 | return new EmptyVoiceSynthesisTask(data); 52 | } 53 | 54 | string mID; 55 | } 56 | 57 | static OrderedMap mAutomationConfigs = new(); 58 | static OrderedMap mPartProperties = new(); 59 | static OrderedMap mNoteProperties = new(); 60 | static VoiceSourceInfo mVoiceSourceInfo = new() { Name = "Empty Voice", Description = "" }; 61 | } 62 | -------------------------------------------------------------------------------- /TuneLab/Extensions/Voices/EmptyVoiceSynthesisTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Structures; 7 | 8 | namespace TuneLab.Extensions.Voices; 9 | 10 | internal class EmptyVoiceSynthesisTask : ISynthesisTask 11 | { 12 | public event Action? Complete; 13 | public event Action? Progress; 14 | public event Action? Error; 15 | 16 | public EmptyVoiceSynthesisTask(ISynthesisData piece) 17 | { 18 | mStartTime = piece.StartTime(); 19 | } 20 | 21 | public void Start() 22 | { 23 | Complete?.Invoke(new SynthesisResult(mStartTime, 44100, [])); 24 | } 25 | 26 | public void Suspend() 27 | { 28 | 29 | } 30 | 31 | public void Resume() 32 | { 33 | 34 | } 35 | 36 | public void Stop() 37 | { 38 | 39 | } 40 | 41 | public void SetDirty(string dirtyType) 42 | { 43 | 44 | } 45 | 46 | double mStartTime; 47 | } 48 | -------------------------------------------------------------------------------- /TuneLab/GUI/Alignment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.GUI; 8 | 9 | internal static class HorizontalAlignment 10 | { 11 | public const int Left = 0; 12 | public const int Center = 1; 13 | public const int Right = 2; 14 | } 15 | 16 | internal static class VerticalAlignment 17 | { 18 | public const int Top = 0; 19 | public const int Center = 4; 20 | public const int Bottom = 8; 21 | } 22 | 23 | internal static class Alignment 24 | { 25 | public const int LeftTop = HorizontalAlignment.Left | VerticalAlignment.Top; 26 | public const int CenterTop = HorizontalAlignment.Center | VerticalAlignment.Top; 27 | public const int RightTop = HorizontalAlignment.Right | VerticalAlignment.Top; 28 | public const int LeftCenter = HorizontalAlignment.Left | VerticalAlignment.Center; 29 | public const int Center = HorizontalAlignment.Center | VerticalAlignment.Center; 30 | public const int RightCenter = HorizontalAlignment.Right | VerticalAlignment.Center; 31 | public const int LeftBottom = HorizontalAlignment.Left | VerticalAlignment.Bottom; 32 | public const int CenterBottom = HorizontalAlignment.Center | VerticalAlignment.Bottom; 33 | public const int RightBottom = HorizontalAlignment.Right | VerticalAlignment.Bottom; 34 | public static (double, double) Offset(this int alignment, double width, double height) 35 | { 36 | return (width / -2 * (alignment % 4), height / -2 * (alignment >> 2)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TuneLab/GUI/ColorSet.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Media; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.GUI; 9 | 10 | internal struct ColorSet 11 | { 12 | public Color Color { get; set; } 13 | public Color? HoveredColor { get; set; } 14 | public Color? PressedColor { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab/GUI/Components/CheckBox.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Media; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TuneLab.Base.Properties; 9 | using TuneLab.GUI; 10 | using TuneLab.GUI.Components; 11 | 12 | namespace TuneLab.GUI.Components; 13 | 14 | internal class CheckBox : Toggle, IDataValueController 15 | { 16 | public Color Background { get => mBackContent.CheckedColorSet.Color; set { mBackContent.CheckedColorSet.Color = value; InvalidateVisual(); } } 17 | public SvgIcon? CheckIcon { get => mCheckItem.Icon; set { mCheckItem.Icon = value; InvalidateVisual(); } } 18 | public CheckBox() 19 | { 20 | Width = 16; 21 | Height = 16; 22 | AddContent(new() { Item = new IconItem() { Icon = Assets.CheckBoxFrame }, UncheckedColorSet = new() { Color = Colors.White } }); 23 | AddContent(mBackContent); 24 | AddContent(new() { Item = mCheckItem, CheckedColorSet = new() { Color = Colors.White } }); 25 | } 26 | 27 | public void DisplayNull() 28 | { 29 | Background = Colors.Transparent; 30 | CheckIcon = Assets.Hyphen; 31 | Display(false); 32 | } 33 | 34 | public void DisplayMultiple() 35 | { 36 | Background = Style.HIGH_LIGHT; 37 | CheckIcon = Assets.Hyphen; 38 | Display(true); 39 | } 40 | 41 | readonly ToggleContent mBackContent = new() { Item = new BorderItem() { CornerRadius = 4 }, CheckedColorSet = new() { Color = Style.HIGH_LIGHT } }; 42 | readonly IconItem mCheckItem = new() { Icon = Assets.Check }; 43 | } 44 | -------------------------------------------------------------------------------- /TuneLab/GUI/Components/CollapsiblePanel.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Media; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TuneLab.GUI.Components; 10 | 11 | internal class CollapsiblePanel : StackPanel 12 | { 13 | public Control? Title 14 | { 15 | get => mTitlePanel.Children.FirstOrDefault(); 16 | set { mTitlePanel.Children.Clear(); if (value != null) mTitlePanel.Children.Add(value); } 17 | } 18 | 19 | public Control? Content 20 | { 21 | get => mContentPanel.Children.FirstOrDefault(); 22 | set { mContentPanel.Children.Clear(); if (value != null) mContentPanel.Children.Add(value); } 23 | } 24 | 25 | public CollapsiblePanel() 26 | { 27 | Children.Add(mTitlePanel); 28 | Children.Add(mContentPanel); 29 | 30 | mTitlePanel.PointerPressed += (s, e) => { mContentPanel.IsVisible = !mContentPanel.IsVisible; }; 31 | } 32 | 33 | LayerPanel mTitlePanel = new() { Background = Brushes.Transparent } ; 34 | LayerPanel mContentPanel = new(); 35 | } 36 | -------------------------------------------------------------------------------- /TuneLab/GUI/Components/IComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.GUI.Components; 8 | 9 | internal interface IComponent 10 | { 11 | public bool IsHover { get; } 12 | public bool IsPrimaryButtonDragging { get; } 13 | public bool IsMiddleButtonDragging { get; } 14 | public bool IsSecondaryButtonDragging { get; } 15 | public bool IsPressed { get; } 16 | public long DoubleClickInterval { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /TuneLab/GUI/Components/LayerPanel.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TuneLab.GUI.Components; 10 | 11 | internal class LayerPanel : Panel 12 | { 13 | protected override Size ArrangeOverride(Size finalSize) 14 | { 15 | foreach (var child in Children) 16 | { 17 | child.Arrange(new(finalSize)); 18 | } 19 | 20 | return finalSize; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TuneLab/GUI/Components/MovableComponent.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Media; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Event; 8 | using TuneLab.Base.Structures; 9 | using TuneLab.GUI.Input; 10 | using TuneLab.Utils; 11 | 12 | namespace TuneLab.GUI.Components; 13 | 14 | internal class MovableComponent : Component 15 | { 16 | public IActionEvent MoveStart => mMoveStart; 17 | public IActionEvent MoveEnd => mMoveEnd; 18 | public IActionEvent Moved => mMoved; 19 | 20 | public override void Render(DrawingContext context) 21 | { 22 | context.FillRectangle(Brushes.Transparent, this.Rect()); 23 | } 24 | 25 | protected override void OnMouseDown(MouseDownEventArgs e) 26 | { 27 | if (e.MouseButtonType != MouseButtonType.PrimaryButton) 28 | return; 29 | 30 | mDownOffset = e.Position; 31 | CorrectCursor(); 32 | mMoveStart.Invoke(); 33 | } 34 | 35 | protected override void OnMouseMove(MouseMoveEventArgs e) 36 | { 37 | if (!IsPrimaryButtonPressed) 38 | return; 39 | 40 | mMoved.Invoke(e.Position - mDownOffset + Bounds.Position); 41 | } 42 | 43 | protected override void OnMouseUp(MouseUpEventArgs e) 44 | { 45 | if (e.MouseButtonType != MouseButtonType.PrimaryButton) 46 | return; 47 | 48 | CorrectCursor(); 49 | mMoveEnd.Invoke(); 50 | } 51 | 52 | protected override void OnMouseEnter(MouseEnterEventArgs e) 53 | { 54 | if (IsPressed) 55 | return; 56 | 57 | CorrectCursor(); 58 | } 59 | 60 | protected override void OnMouseLeave(MouseLeaveEventArgs e) 61 | { 62 | if (IsPressed) 63 | return; 64 | 65 | CorrectCursor(); 66 | } 67 | 68 | void CorrectCursor() 69 | { 70 | if (IsPressed) 71 | { 72 | // CloseHand 73 | return; 74 | } 75 | 76 | if (IsHover) 77 | { 78 | // OpenHand 79 | return; 80 | } 81 | 82 | Cursor = null; 83 | } 84 | 85 | Avalonia.Point mDownOffset; 86 | 87 | readonly ActionEvent mMoveStart = new(); 88 | readonly ActionEvent mMoveEnd = new(); 89 | readonly ActionEvent mMoved = new(); 90 | } 91 | -------------------------------------------------------------------------------- /TuneLab/GUI/Controllers/IPropertyController.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.GUI.Controllers; 9 | 10 | internal interface IPropertyController 11 | { 12 | void Terminate(); 13 | } 14 | 15 | internal interface IPropertyController : IPropertyController 16 | { 17 | void Setup(T value); 18 | } 19 | -------------------------------------------------------------------------------- /TuneLab/GUI/Controllers/SingleLineTextController.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Primitives; 4 | using Avalonia.Interactivity; 5 | using Avalonia.Layout; 6 | using Avalonia.Markup.Xaml.Templates; 7 | using Avalonia.Media; 8 | using Avalonia.Styling; 9 | using DynamicData; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Diagnostics.CodeAnalysis; 13 | using System.Linq; 14 | using System.Text; 15 | using System.Threading.Tasks; 16 | using TuneLab.Base.Event; 17 | using TuneLab.Base.Properties; 18 | using TuneLab.GUI.Components; 19 | 20 | namespace TuneLab.GUI.Controllers; 21 | 22 | internal class SingleLineTextController : LayerPanel, IDataValueController 23 | { 24 | public IActionEvent ValueWillChange => mTextInput.EnterInput; 25 | public IActionEvent ValueChanged => mTextInput.TextChanged; 26 | public IActionEvent ValueCommited => mTextInput.EndInput; 27 | public string Value { get => mTextInput.Text; set => mTextInput.Text = value; } 28 | 29 | public SingleLineTextController() 30 | { 31 | mTextInput = new TextInput() 32 | { 33 | Height = 28, 34 | AcceptsReturn = false 35 | }; 36 | 37 | Children.Add(mTextInput); 38 | } 39 | 40 | public void Display(string text) 41 | { 42 | mTextInput.Display(text); 43 | } 44 | 45 | public void DisplayNull() 46 | { 47 | mTextInput.Display("-"); 48 | } 49 | 50 | public void DisplayMultiple() 51 | { 52 | mTextInput.Display("(Multiple)"); 53 | } 54 | 55 | readonly TextInput mTextInput; 56 | } 57 | -------------------------------------------------------------------------------- /TuneLab/GUI/Dialog.axaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /TuneLab/GUI/IItem.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Media; 2 | using Avalonia; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TuneLab.GUI.Components; 9 | using TuneLab.GUI; 10 | using TuneLab.Utils; 11 | 12 | namespace TuneLab.GUI; 13 | 14 | internal interface IItem 15 | { 16 | void Paint(DrawingContext context, Rect rect, Color color); 17 | } 18 | 19 | internal class BorderItem : IItem 20 | { 21 | public double CornerRadius { get; set; } = 4; 22 | 23 | public void Paint(DrawingContext context, Rect rect, Color color) 24 | { 25 | context.DrawRectangle(color.ToBrush(), null, rect, CornerRadius, CornerRadius); 26 | } 27 | } 28 | 29 | internal class TextItem : IItem 30 | { 31 | public double FontSize { get; set; } = 12; 32 | public string Text { get; set; } = string.Empty; 33 | public Point Offset { get; set; } = new Point(); 34 | public int PivotAlignment { get; set; } = GUI.Alignment.Center; 35 | public int Alignment { get; set; } = GUI.Alignment.Center; 36 | 37 | public void Paint(DrawingContext context, Rect rect, Color color) 38 | { 39 | context.DrawString(Text, rect, color.ToBrush(), FontSize, Alignment, PivotAlignment, Offset); 40 | } 41 | } 42 | 43 | internal class IconItem : IItem 44 | { 45 | public SvgIcon? Icon { get; set; } 46 | public int PivotAlignment { get; set; } = GUI.Alignment.Center; 47 | public int Alignment { get; set; } = GUI.Alignment.Center; 48 | public Point Offset { get; set; } = new Point(); 49 | public double Scale { get; set; } = 1; 50 | 51 | public void Paint(DrawingContext context, Rect rect, Color color) 52 | { 53 | if (Icon == null) 54 | return; 55 | 56 | var image = Icon.GetImage(color); 57 | var size = image.Size; 58 | size *= Scale; 59 | var anchor = Alignment.Offset(rect.Width, rect.Height); 60 | var pivot = Alignment.Offset(size.Width, size.Height); 61 | context.DrawImage(Icon.GetImage(color), new Rect(Offset.X - anchor.Item1 + pivot.Item1, Offset.Y - anchor.Item2 + pivot.Item2, size.Width, size.Height)); 62 | } 63 | } -------------------------------------------------------------------------------- /TuneLab/GUI/IScrollAxis.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TuneLab.GUI; 4 | 5 | internal interface IScrollAxis 6 | { 7 | event Action AxisChanged; 8 | double ViewLength { get; set; } 9 | double ViewOffset { get; set; } 10 | double ContentLength { get; } 11 | } 12 | 13 | internal static class IScrollAxisExtension 14 | { 15 | public static Base.Structures.RangeF ViewRange(this IScrollAxis axis) 16 | { 17 | return new Base.Structures.RangeF(axis.ViewOffset, Math.Min(axis.ViewOffset + axis.ViewLength, axis.ContentLength)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TuneLab/GUI/IScrollView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.GUI; 8 | 9 | internal interface IScrollView 10 | { 11 | IScrollAxis HorizontalAxis { get; } 12 | IScrollAxis VerticalAxis { get; } 13 | } 14 | -------------------------------------------------------------------------------- /TuneLab/GUI/Input/EventArgs.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.GUI.Input; 9 | 10 | internal class EventArgs 11 | { 12 | 13 | } 14 | 15 | internal class MouseEventArgs : EventArgs 16 | { 17 | public ModifierKeys KeyModifiers { get; set; } 18 | public Point Position { get; set; } 19 | } 20 | 21 | internal class MouseButtonEventArgs : MouseEventArgs 22 | { 23 | public MouseButtonType MouseButtonType { get; set; } 24 | } 25 | 26 | internal class MouseDownEventArgs : MouseButtonEventArgs { public bool IsDoubleClick { get; set; } } 27 | internal class MouseMoveEventArgs : MouseEventArgs { } 28 | internal class MouseUpEventArgs : MouseButtonEventArgs { public bool IsClick { get; set; } } 29 | internal class MouseEnterEventArgs : MouseEventArgs { } 30 | internal class MouseLeaveEventArgs : MouseEventArgs { } 31 | 32 | internal class WheelEventArgs : MouseEventArgs 33 | { 34 | public Point Delta { get; set; } 35 | } -------------------------------------------------------------------------------- /TuneLab/GUI/Input/ModifierKeys.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Input; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TuneLab.GUI.Input; 10 | 11 | [Flags] 12 | internal enum ModifierKeys : uint 13 | { 14 | None = 0x0u, 15 | Ctrl = 0x1u, 16 | Alt = 0x2u, 17 | Shift = 0x4u, 18 | } 19 | 20 | internal static class KeyModifierExtension 21 | { 22 | public static ModifierKeys ToModifierKeys(this KeyModifiers keyModifiers) 23 | { 24 | ModifierKeys modifierKeys = ModifierKeys.None; 25 | 26 | if ((uint)(keyModifiers & KeyModifiers.Alt) != 0) 27 | modifierKeys |= ModifierKeys.Alt; 28 | if ((uint)(keyModifiers & KeyModifiers.Control) != 0) 29 | modifierKeys |= ModifierKeys.Ctrl; 30 | if ((uint)(keyModifiers & KeyModifiers.Shift) != 0) 31 | modifierKeys |= ModifierKeys.Shift; 32 | if ((uint)(keyModifiers & KeyModifiers.Meta) != 0) 33 | modifierKeys |= ModifierKeys.Ctrl; 34 | 35 | return modifierKeys; 36 | } 37 | 38 | public static KeyModifiers ToAvalonia(this ModifierKeys modifierKeys) 39 | { 40 | KeyModifiers keyModifiers = KeyModifiers.None; 41 | 42 | if ((modifierKeys & ModifierKeys.Alt) != 0) 43 | keyModifiers |= KeyModifiers.Alt; 44 | if ((modifierKeys & ModifierKeys.Shift) != 0) 45 | keyModifiers |= KeyModifiers.Shift; 46 | if ((modifierKeys & ModifierKeys.Ctrl) != 0) 47 | keyModifiers |= RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 48 | ? KeyModifiers.Control 49 | : KeyModifiers.Meta; 50 | 51 | return keyModifiers; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /TuneLab/GUI/Input/MouseButtonType.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Input; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.GUI.Input; 9 | 10 | internal enum MouseButtonType 11 | { 12 | Other, 13 | PrimaryButton, 14 | MiddleButton, 15 | SecondaryButton, 16 | } 17 | 18 | internal static class MouseButtonTypeExtension 19 | { 20 | public static MouseButtonType ToMouseButtonType(this PointerUpdateKind kind) 21 | { 22 | return kind switch 23 | { 24 | PointerUpdateKind.LeftButtonPressed or PointerUpdateKind.LeftButtonReleased => MouseButtonType.PrimaryButton, 25 | PointerUpdateKind.RightButtonPressed or PointerUpdateKind.RightButtonReleased => MouseButtonType.SecondaryButton, 26 | PointerUpdateKind.MiddleButtonPressed or PointerUpdateKind.MiddleButtonReleased => MouseButtonType.MiddleButton, 27 | _ => MouseButtonType.Other, 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TuneLab/GUI/Style.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Avalonia.Media; 7 | 8 | namespace TuneLab.GUI; 9 | 10 | internal static class Style 11 | { 12 | public static readonly Color TRANSPARENT = new(0, 0, 0, 0); 13 | public static readonly Color WHITE = new(255, 255, 255, 255); 14 | public static readonly Color LIGHT_WHITE = new(255, 181, 181, 191); 15 | public static readonly Color BLACK = new(255, 0, 0, 0); 16 | public static readonly Color TOOL_TIP_BACK = new(255, 57, 57, 64); 17 | public static readonly Color DARK = new(255, 18, 18, 26); 18 | public static readonly Color BACK = new(255, 27, 27, 36); 19 | public static readonly Color INTERFACE = new(255, 39, 39, 54); 20 | public static readonly Color LINE = new((int)(0.2 * 255), 196, 196, 196); 21 | public static readonly Color ITEM = new(255, 58, 63, 105); 22 | public static readonly Color HIGH_LIGHT = new(255, 98, 111, 252); 23 | public static readonly Color TEXT_NORMAL = new((int)(0.7 * 255), 255, 255, 255); 24 | public static readonly Color TEXT_LIGHT = new(255, 255, 255, 255); 25 | public static readonly Color WHITE_KEY = new(255, 39, 39, 54); 26 | public static readonly Color BLACK_KEY = new(255, 27, 27, 36); 27 | public static readonly Color BUTTON_PRIMARY = new(255, 96, 96, 192); 28 | public static readonly Color BUTTON_NORMAL = new(255, 58, 63, 105); 29 | public static readonly Color BUTTON_PRIMARY_HOVER = new(255, 127, 127, 255); 30 | public static readonly Color BUTTON_NORMAL_HOVER = new(255, 85, 92, 153); 31 | 32 | public static readonly Color AMP_NORMAL = new(255, 102, 255, 51); 33 | public static readonly Color AMP_DELAY = new((int)(0.5 * 255), 102, 255, 51); 34 | 35 | public static readonly string[] TRACK_COLORS = 36 | [ 37 | "#737CE5", 38 | "#73B5E5", 39 | "#73E5DB", 40 | "#73E5A2", 41 | "#7DE573", 42 | "#B5E573", 43 | "#E5DB73", 44 | "#E5A273", 45 | "#E5737D", 46 | "#E573B5", 47 | "#DB73E5", 48 | "#A273E5", 49 | ]; 50 | 51 | public static Color DefaultTrackColor => Color.Parse(TRACK_COLORS[0]); 52 | 53 | public static string GetNewColor(int index) 54 | { 55 | return TRACK_COLORS[index % TRACK_COLORS.Length]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /TuneLab/GUI/SvgIcon.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Media; 2 | using Avalonia.Svg.Skia; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace TuneLab.GUI; 11 | 12 | internal class SvgIcon(string content) 13 | { 14 | public SvgImage GetImage(Color color) 15 | { 16 | return new SvgImage() { Source = SvgSource.LoadFromSvg(content.Replace("white", string.Format(rgba, color.R, color.G, color.B, (color.A / 255.0).ToString(new CultureInfo("zh-CN"))))) }; 17 | } 18 | 19 | static readonly string rgba = "rgba({0},{1},{2},{3})"; 20 | } 21 | -------------------------------------------------------------------------------- /TuneLab/GUI/UpdateDialog.axaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /TuneLab/I18N/DummyTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.I18N; 8 | 9 | internal class DummyTranslator : ITranslator 10 | { 11 | public string Translate(string text, IEnumerable context) 12 | { 13 | return text; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab/I18N/ITranslationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.I18N; 8 | 9 | internal interface ITranslationContext 10 | { 11 | public IEnumerable TranslationContextKeys => [GetType().Name]; 12 | } 13 | 14 | internal static class ITranslateContextExtension 15 | { 16 | public static string Tr(this string text, object context) 17 | { 18 | return TranslationManager.CurrentTranslator.Translate(text, [context.GetType().Name]); 19 | } 20 | 21 | public static string Tr(this ITranslationContext translationContext, string text, params string[] context) 22 | { 23 | return TranslationManager.CurrentTranslator.Translate(text, translationContext.TranslationContextKeys.Concat(context)); 24 | } 25 | 26 | public static string Tr(this string text, params string[] context) 27 | { 28 | return TranslationManager.CurrentTranslator.Translate(text, context); 29 | } 30 | 31 | public static string Tr(this string text, ITranslationContext context) 32 | { 33 | return TranslationManager.CurrentTranslator.Translate(text, context.TranslationContextKeys); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TuneLab/I18N/ITranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.I18N; 8 | 9 | internal interface ITranslator 10 | { 11 | string Translate(string text, IEnumerable context); 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab/I18N/TranslationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.I18N; 8 | 9 | internal class TranslationContext(IEnumerable context) : ITranslationContext 10 | { 11 | public static implicit operator TranslationContext(string[] context) => new(context); 12 | public static implicit operator TranslationContext(string context) => new([context]); 13 | 14 | public IEnumerable TranslationContextKeys => context; 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab/I18N/TranslationManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TuneLab.Base.Event; 9 | 10 | namespace TuneLab.I18N; 11 | 12 | internal static class TranslationManager 13 | { 14 | static readonly ITranslator DefaultTranslator = new DummyTranslator(); 15 | public static IReadOnlyList Languages => mLanguages; 16 | public static NotifiableProperty CurrentLanguage { get; } = string.Empty; 17 | public static ITranslator CurrentTranslator { get; private set; } = DefaultTranslator; 18 | 19 | static TranslationManager() 20 | { 21 | CurrentLanguage.Modified.Subscribe(() => { CurrentTranslator = GetTranslator(CurrentLanguage); }); 22 | } 23 | 24 | public static void Init(string path) 25 | { 26 | DirectoryInfo resDir = new DirectoryInfo(path); 27 | if (!resDir.Exists) return; 28 | foreach (var file in resDir.GetFiles("*.toml")) 29 | { 30 | var i18nName = Path.GetFileNameWithoutExtension(file.FullName); 31 | if (mTranslators.ContainsKey(i18nName)) 32 | continue; 33 | 34 | var translator = new TomlTranslator(file.FullName); 35 | mTranslators.Add(i18nName, translator); 36 | mLanguages.Add(i18nName); 37 | } 38 | } 39 | 40 | public static string GetCurrentOSLanguage() 41 | { 42 | CultureInfo currentCulture = CultureInfo.CurrentCulture; 43 | string currentLanguage = currentCulture.Name; 44 | return currentLanguage; 45 | } 46 | 47 | public static ITranslator GetTranslator(string language) 48 | { 49 | if (mTranslators.TryGetValue(language, out var translator)) 50 | return translator; 51 | 52 | return DefaultTranslator; 53 | } 54 | 55 | static Dictionary mTranslators = []; 56 | static List mLanguages = []; 57 | } 58 | -------------------------------------------------------------------------------- /TuneLab/PathManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab; 9 | 10 | internal static class PathManager 11 | { 12 | public static string AppDataFolder => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); 13 | public static string TuneLabFolder => Path.Combine(AppDataFolder, "TuneLab"); 14 | public static string AutoSaveFolder => Path.Combine(TuneLabFolder, "AutoSave"); 15 | public static string LogsFolder => Path.Combine(TuneLabFolder, "Logs"); 16 | public static string ConfigsFolder => Path.Combine(TuneLabFolder, "Configs"); 17 | public static string SettingsFilePath => Path.Combine(ConfigsFolder, "Settings.json"); 18 | public static string ExtensionsFolder => Path.Combine(TuneLabFolder, "Extensions"); 19 | public static string LockFilePath => Path.Combine(TuneLabFolder, "TuneLab.lock"); 20 | public static string LogFilePath { get { mLogFilePath ??= Path.Combine(LogsFolder, "TuneLab_" + DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss") + ".log"); return mLogFilePath; } } 21 | public static string ExcutableFolder => AppDomain.CurrentDomain.BaseDirectory; 22 | public static string ResourcesFolder => Path.Combine(ExcutableFolder, "Resources"); 23 | public static string TranslationsFolder => Path.Combine(ResourcesFolder, "Translations"); 24 | 25 | public static void MakeSureExist(string folder) 26 | { 27 | if (!Directory.Exists(folder)) 28 | Directory.CreateDirectory(folder); 29 | } 30 | 31 | static string? mLogFilePath = null; 32 | } 33 | -------------------------------------------------------------------------------- /TuneLab/Resources/Translations/en-US.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYunPlayer/TuneLab/e212ec9d953bd00103dd48e3886deb848cd0a893/TuneLab/Resources/Translations/en-US.toml -------------------------------------------------------------------------------- /TuneLab/TranslationContexts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.I18N; 7 | 8 | namespace TuneLab; 9 | 10 | internal static class TC 11 | { 12 | public static readonly TranslationContext Dialog = "Dialog"; 13 | public static readonly TranslationContext Property = "Property"; 14 | public static readonly TranslationContext Menu = "Menu"; 15 | public static readonly TranslationContext Document = "Document"; 16 | } 17 | -------------------------------------------------------------------------------- /TuneLab/UI/LyricInput/LyricInput.axaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/Common/PlayheadLayer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Media; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TuneLab.GUI.Components; 9 | using TuneLab.Data; 10 | 11 | namespace TuneLab.UI; 12 | 13 | internal class PlayheadLayer : Component 14 | { 15 | public interface IDependency 16 | { 17 | IPlayhead Playhead { get; } 18 | TickAxis TickAxis { get; } 19 | } 20 | 21 | public PlayheadLayer(IDependency dependency) 22 | { 23 | mDependency = dependency; 24 | 25 | IsHitTestVisible = false; 26 | 27 | Playhead.PosChanged.Subscribe(InvalidateVisual); 28 | TickAxis.AxisChanged += InvalidateVisual; 29 | } 30 | 31 | ~PlayheadLayer() 32 | { 33 | Playhead.PosChanged.Unsubscribe(InvalidateVisual); 34 | TickAxis.AxisChanged -= InvalidateVisual; 35 | } 36 | 37 | public override void Render(DrawingContext context) 38 | { 39 | context.FillRectangle(Brushes.White, new Rect(TickAxis.Tick2X(Playhead.Pos), 0, 1, Bounds.Height)); 40 | } 41 | 42 | IPlayhead Playhead => mDependency.Playhead; 43 | TickAxis TickAxis => mDependency.TickAxis; 44 | 45 | readonly IDependency mDependency; 46 | } 47 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/Common/TickAxis.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.Base.Event; 7 | using TuneLab.GUI; 8 | using TuneLab.Animation; 9 | using TuneLab.Data; 10 | using TuneLab.Utils; 11 | using TuneLab.Base.Utils; 12 | 13 | namespace TuneLab.UI; 14 | 15 | internal class TickAxis : AnimationScalableScrollAxis 16 | { 17 | public double PixelsPerTick => Factor; 18 | public double TicksPerPixel => 1 / Factor; 19 | public double MinVisibleTick => Pos2Tick(MinVisiblePos); 20 | public double MaxVisibleTick => Pos2Tick(MaxVisiblePos); 21 | 22 | public TickAxis() 23 | { 24 | ContentSize = int.MaxValue; 25 | Factor = ScaleLevel2Factor(ScaleLevel); 26 | } 27 | 28 | ~TickAxis() 29 | { 30 | s.DisposeAll(); 31 | } 32 | 33 | public double Tick2X(double tick) 34 | { 35 | return Pos2Coor(tick); 36 | } 37 | public double X2Tick(double x) 38 | { 39 | return Coor2Pos(x); 40 | } 41 | 42 | public void MoveTickToX(double tick, double x) 43 | { 44 | MovePosToCoor(Tick2Pos(tick), x); 45 | } 46 | 47 | public void AnimateMoveTickToX(double tick, double x, double millisec = 150, IAnimationCurve? curve = null) 48 | { 49 | AnimateMovePosToCoor(Tick2Pos(tick), x, millisec, curve); 50 | } 51 | 52 | protected override double ScaleLevel2Factor(double level) 53 | { 54 | return DEFAULT_PPT * Math.Pow(SAMPLE_PPT / DEFAULT_PPT, ScaleLevel / SAMPLE_SCALE_LEVEL); 55 | } 56 | 57 | protected override double MinScaleLevel => MIN_SCALE_LEVEL; 58 | protected override double MaxScaleLevel => MAX_SCALE_LEVEL; 59 | 60 | double Tick2Pos(double tick) => tick; 61 | double Pos2Tick(double pos) => pos; 62 | 63 | const double MIN_SCALE_LEVEL = -8; 64 | const double MAX_SCALE_LEVEL = 8; 65 | const double DEFAULT_PPT = 0.25; 66 | const double SAMPLE_PPT = 4; 67 | const double SAMPLE_SCALE_LEVEL = 8; 68 | 69 | readonly DisposableManager s = new(); 70 | } 71 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/Common/TrackColorManager.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Media; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using TuneLab.Configs; 9 | using TuneLab.Data; 10 | using TuneLab.GUI; 11 | 12 | namespace TuneLab.UI; 13 | 14 | internal static class TrackColorManager 15 | { 16 | public static double HueChangeRate 17 | { 18 | get => mHueChangeRate; 19 | set 20 | { 21 | if (mHueChangeRate == value) 22 | return; 23 | 24 | mHueChangeRate = value; 25 | if (mHueChangeRate == 0) 26 | timer.Stop(); 27 | else 28 | timer.Start(); 29 | } 30 | } 31 | 32 | public static Color GetColor(this ITrack track) 33 | { 34 | var color = GetFixedColor(track); 35 | var hsv = color.ToHsv(); 36 | var newHSV = new HsvColor(hsv.A, (hsv.H + offset) % 360, hsv.S, hsv.V); 37 | return newHSV.ToRgb(); 38 | } 39 | 40 | public static Color GetFixedColor(this ITrack track) 41 | { 42 | if (Color.TryParse(track.Color.Value, out var color)) 43 | return color; 44 | 45 | return Style.DefaultTrackColor; 46 | } 47 | 48 | public static void RegisterOnTrackColorUpdated(this Avalonia.Visual visual, Action? action = null) 49 | { 50 | var context = SynchronizationContext.Current; 51 | if (context == null) 52 | return; 53 | 54 | timer.Elapsed += (s, e) => { context.Post(_ => { visual.InvalidateVisual(); action?.Invoke(); }, null); }; 55 | } 56 | 57 | static TrackColorManager() 58 | { 59 | timer.Elapsed += (s, e) => 60 | { 61 | offset += HueChangeRate / 1000 * timer.Interval; 62 | offset %= 360; 63 | if (offset < 0) 64 | offset += 360; 65 | }; 66 | HueChangeRate = Settings.TrackHueChangeRate; 67 | Settings.TrackHueChangeRate.Modified.Subscribe(() => HueChangeRate = Settings.TrackHueChangeRate); 68 | } 69 | 70 | static System.Timers.Timer timer = new System.Timers.Timer() { Interval = 16.5 }; 71 | static double offset = 0; 72 | static double mHueChangeRate = 0; 73 | } 74 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/FunctionBar/PlayScrollTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.UI; 8 | 9 | internal enum PlayScrollTarget 10 | { 11 | None, 12 | View, 13 | Playhead, 14 | } 15 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/PianoWindow/ParameterArea/AutomationRendererItem.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Data; 8 | 9 | namespace TuneLab.UI; 10 | 11 | internal partial class AutomationRenderer 12 | { 13 | class AutomationRenderItem(AutomationRenderer automationRenderer) : Item 14 | { 15 | public AutomationRenderer AutomationRenderer => automationRenderer; 16 | } 17 | 18 | class VibratoItem(AutomationRenderer automationRenderer) : AutomationRenderItem(automationRenderer) 19 | { 20 | public required Vibrato Vibrato; 21 | 22 | public override bool Raycast(Point point) 23 | { 24 | double left = AutomationRenderer.TickAxis.Tick2X(Vibrato.GlobalStartPos()); 25 | double right = AutomationRenderer.TickAxis.Tick2X(Vibrato.GlobalEndPos()); 26 | return point.X > left && point.X < right; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/PianoWindow/ParameterArea/ParameterContainer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TuneLab.Base.Structures; 9 | using TuneLab.GUI; 10 | using TuneLab.Base.Utils; 11 | 12 | namespace TuneLab.UI; 13 | 14 | internal class ParameterContainer : Panel 15 | { 16 | public AutomationRenderer AutomationRenderer => mAutomationRenderer; 17 | 18 | public ParameterContainer(AutomationRenderer.IDependency dependency) 19 | { 20 | mAutomationRenderer = new AutomationRenderer(dependency); 21 | Children.Add(mAutomationRenderer); 22 | } 23 | 24 | protected override Size ArrangeOverride(Size finalSize) 25 | { 26 | mAutomationRenderer.Arrange(new(finalSize)); 27 | return finalSize; 28 | } 29 | 30 | readonly AutomationRenderer mAutomationRenderer; 31 | } 32 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/PianoWindow/ParameterArea/ParameterTitleBar.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Layout; 4 | using Avalonia.Media; 5 | using Avalonia.Input; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using TuneLab.GUI; 12 | using TuneLab.GUI.Components; 13 | using TuneLab.GUI.Input; 14 | using TuneLab.Utils; 15 | 16 | namespace TuneLab.UI; 17 | 18 | internal class ParameterTitleBar : MovableComponent 19 | { 20 | public new event Action? Moved; 21 | 22 | public ParameterTitleBar() 23 | { 24 | base.Moved.Subscribe(p => Moved?.Invoke(p.Y)); 25 | } 26 | 27 | public override void Render(DrawingContext context) 28 | { 29 | context.FillRectangle(new Color(255, 51, 51, 64).ToBrush(), this.Rect()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/PianoWindow/PianoRoll/PianoRoll.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Input; 3 | using Avalonia.Media; 4 | using Avalonia; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using TuneLab.GUI; 11 | using TuneLab.GUI.Components; 12 | using TuneLab.GUI.Input; 13 | using TuneLab.Utils; 14 | using TuneLab.Base.Science; 15 | 16 | namespace TuneLab.UI; 17 | 18 | internal partial class PianoRoll : View 19 | { 20 | public interface IDependency 21 | { 22 | PitchAxis PitchAxis { get; } 23 | } 24 | 25 | public PianoRoll(IDependency dependency) 26 | { 27 | mDependency = dependency; 28 | 29 | mMiddleDragOperation = new(this); 30 | mPlayKeySampleOperation = new(this); 31 | 32 | PitchAxis.AxisChanged += InvalidateVisual; 33 | } 34 | 35 | ~PianoRoll() 36 | { 37 | PitchAxis.AxisChanged -= InvalidateVisual; 38 | } 39 | 40 | protected override void OnRender(DrawingContext context) 41 | { 42 | context.FillRectangle(Style.BACK.ToBrush(), this.Rect()); 43 | } 44 | 45 | PitchAxis PitchAxis => mDependency.PitchAxis; 46 | 47 | readonly IDependency mDependency; 48 | } 49 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/PianoWindow/PianoScrollView/IPianoScrollView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Avalonia; 7 | using TuneLab.Data; 8 | 9 | namespace TuneLab.UI; 10 | 11 | internal interface IPianoScrollView 12 | { 13 | TickAxis TickAxis { get; } 14 | PitchAxis PitchAxis { get; } 15 | } 16 | 17 | internal static class IPianoScrollViewExtension 18 | { 19 | public static Rect NoteRect(this IPianoScrollView view, INote note) 20 | { 21 | double x = view.TickAxis.Tick2X(note.GlobalStartPos()); 22 | double y = view.PitchAxis.Pitch2Y(note.Pitch.Value + 1); 23 | double w = note.Dur.Value * view.TickAxis.PixelsPerTick; 24 | double h = view.PitchAxis.KeyHeight; 25 | return new Rect(x, y, w, h); 26 | } 27 | 28 | public static Rect ReferNoteRect(this IPianoScrollView view, INote note) 29 | { 30 | double keyHeight = view.PitchAxis.KeyHeight / 7; 31 | double keyOffset = (view.PitchAxis.KeyHeight - keyHeight) / 2; 32 | double x = view.TickAxis.Tick2X(note.GlobalStartPos()); 33 | double y = view.PitchAxis.Pitch2Y(note.Pitch.Value + 1) + keyOffset; 34 | double w = note.Dur.Value * view.TickAxis.PixelsPerTick; 35 | double h = keyHeight; 36 | return new Rect(x, y, w, h); 37 | } 38 | } -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/PianoWindow/PianoTool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.UI; 8 | 9 | internal enum PianoTool 10 | { 11 | Note, 12 | Pitch, 13 | Anchor, 14 | Lock, 15 | Vibrato, 16 | Select, 17 | } 18 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/PianoWindow/PitchAxis.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TuneLab.GUI; 7 | using TuneLab.Base.Science; 8 | using TuneLab.Animation; 9 | 10 | namespace TuneLab.UI; 11 | 12 | internal class PitchAxis : AnimationScalableScrollAxis 13 | { 14 | public double KeyHeight => Factor; 15 | public double MinVisiblePitch => Pos2Pitch(MaxVisiblePos); 16 | public double MaxVisiblePitch => Pos2Pitch(MinVisiblePos); 17 | 18 | public PitchAxis() 19 | { 20 | ContentSize = MusicTheory.PITCH_COUNT; 21 | Factor = ScaleLevel2Factor(ScaleLevel); 22 | PivotPos = Pitch2Pos(DEFAULT_VIEW_MID_PITCH + 0.5); 23 | MovePivotToCoor(0); 24 | ViewPivotRatio = 0.5; 25 | } 26 | public double Pitch2Y(double pitch) 27 | { 28 | return Pos2Coor(Pitch2Pos(pitch)); 29 | } 30 | public double Y2Pitch(double y) 31 | { 32 | return Pos2Pitch(Coor2Pos(y)); 33 | } 34 | public void MovePitchToY(double pitch, double y) 35 | { 36 | MovePosToCoor(Pitch2Pos(pitch), y); 37 | } 38 | public void AnimateMovePitchToY(double pitch, double y, double millisec = 150, IAnimationCurve? curve = null) 39 | { 40 | AnimateMovePosToCoor(Pitch2Pos(pitch), y, millisec, curve); 41 | } 42 | 43 | protected override double ScaleLevel2Factor(double level) 44 | { 45 | return DEFAULT_KEY_HEIGHT * Math.Pow(SAMPLE_KEY_HEIGHT / DEFAULT_KEY_HEIGHT, ScaleLevel / SAMPLE_SCALE_LEVEL); ; 46 | } 47 | 48 | protected override double MinScaleLevel => MIN_SCALE_LEVEL; 49 | protected override double MaxScaleLevel => MAX_SCALE_LEVEL; 50 | 51 | double Pitch2Pos(double pitch) { return MusicTheory.MAX_PITCH + 1 - pitch; } 52 | double Pos2Pitch(double pos) { return MusicTheory.MAX_PITCH + 1 - pos; } 53 | 54 | const double MIN_SCALE_LEVEL = -4; 55 | const double MAX_SCALE_LEVEL = 12; 56 | const double DEFAULT_KEY_HEIGHT = 28; 57 | const double SAMPLE_KEY_HEIGHT = 14; 58 | const double SAMPLE_SCALE_LEVEL = -4; 59 | 60 | const double DEFAULT_VIEW_MID_PITCH = MusicTheory.C0_PITCH - MusicTheory.MIN_PITCH + 12 * 4; 61 | } 62 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/SideBar/ISideBarContentProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.UI; 8 | 9 | internal interface ISideBarContentProvider 10 | { 11 | SideBar.SideBarContent Content { get; } 12 | } 13 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/SideBar/Properties/MidiPartFixedController.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection.Emit; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TuneLab.Base.Data; 9 | using TuneLab.Base.Event; 10 | using TuneLab.Data; 11 | using TuneLab.GUI; 12 | using TuneLab.GUI.Controllers; 13 | using TuneLab.Utils; 14 | using TuneLab.Base.Utils; 15 | using TuneLab.Base.Properties; 16 | using TuneLab.Extensions.Formats.DataInfo; 17 | using TuneLab.Base.Structures; 18 | using TuneLab.I18N; 19 | 20 | namespace TuneLab.UI; 21 | 22 | internal class MidiPartFixedController : StackPanel 23 | { 24 | public IMidiPart? Part { get => mPart.Object; set => mPart.Set(value); } 25 | public MidiPartFixedController() 26 | { 27 | Background = Style.INTERFACE.ToBrush(); 28 | Orientation = Avalonia.Layout.Orientation.Vertical; 29 | 30 | AddController(mGainController, "Gain".Tr(TC.Property)); 31 | mGainController.SetRange(-24, +24); 32 | mGainController.SetDefaultValue(0); 33 | mGainController.Bind(mPart.Select(part => part.Gain), s); 34 | 35 | } 36 | 37 | void AddController(Control control, string name) 38 | { 39 | Children.Add(new Avalonia.Controls.Label() 40 | { 41 | Height = 30, 42 | FontSize = 12, 43 | VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Bottom, 44 | Foreground = Style.LIGHT_WHITE.ToBrush(), 45 | Content = name, 46 | Padding = new(24, 0) 47 | }); 48 | Children.Add(control); 49 | Children.Add(new Border() { Height = 1, Background = Style.BACK.ToBrush() }); 50 | } 51 | 52 | readonly Owner mPart = new(); 53 | 54 | readonly SliderController mGainController = new() { Margin = new(24, 12) }; 55 | readonly DisposableManager s = new(); 56 | } 57 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/SideBar/SideBar.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Layout; 4 | using Avalonia.Media; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using TuneLab.GUI.Components; 11 | using TuneLab.GUI; 12 | using TuneLab.Utils; 13 | 14 | namespace TuneLab.UI; 15 | 16 | internal class SideBar : DockPanel 17 | { 18 | public struct SideBarContent 19 | { 20 | public IImage Icon; 21 | public string Name; 22 | public IEnumerable Items; 23 | } 24 | 25 | public SideBar() 26 | { 27 | Focusable = true; 28 | IsTabStop = false; 29 | var title = new DockPanel() { Height = 48, Background = Style.INTERFACE.ToBrush(), VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top }; 30 | { 31 | mIcon = new Image() { Width = 24, Height = 24, Margin = new(24, 12, 16, 12) }; 32 | title.AddDock(mIcon, Dock.Left); 33 | mName = new Label() { FontSize = 16, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, Foreground = Style.TEXT_LIGHT.ToBrush() }; 34 | title.AddDock(mName); 35 | } 36 | this.AddDock(title, Dock.Top); 37 | this.AddDock(new Border() { Height = 1, Background = Style.BACK.ToBrush() }, Dock.Top); 38 | this.AddDock(mListView); 39 | } 40 | 41 | public void SetContent(SideBarContent content) 42 | { 43 | mIcon.Source = content.Icon; 44 | mName.Content = content.Name; 45 | mListView.Content.Children.Clear(); 46 | foreach (var child in content.Items) 47 | { 48 | mListView.Content.Children.Add(child); 49 | } 50 | } 51 | 52 | readonly Image mIcon; 53 | readonly Label mName; 54 | readonly ListView mListView = new(); 55 | } 56 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/SideBar/SideBarTab.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.UI; 8 | 9 | internal enum SideBarTab 10 | { 11 | None, 12 | Extensions, 13 | Properties, 14 | Export, 15 | } 16 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/Editor/SideBar/SideTabBar.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Media; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.GUI.Components; 8 | using TuneLab.Utils; 9 | using TuneLab.GUI; 10 | using System.Reflection; 11 | using Tomlyn; 12 | using TuneLab.Base.Event; 13 | using Avalonia.Controls; 14 | using TuneLab.I18N; 15 | 16 | namespace TuneLab.UI; 17 | 18 | internal class SideTabBar : ListView 19 | { 20 | public INotifiableProperty SelectedTab = new NotifiableProperty(SideBarTab.None); 21 | 22 | public SideTabBar() 23 | { 24 | Width = 48; 25 | var hoverBack = Colors.White.Opacity(0.05); 26 | 27 | void AddTab(SideBarTab tab, string tooltip, SvgIcon icon) 28 | { 29 | var toggle = new Toggle() { Width = 48, Height = 48 } 30 | .AddContent(new() { Item = new IconItem() { Icon = icon }, CheckedColorSet = new() { Color = Colors.White }, UncheckedColorSet = new() { Color = Style.LIGHT_WHITE.Opacity(0.5), HoveredColor = Style.LIGHT_WHITE } }); 31 | void OnTabChanged() 32 | { 33 | toggle.Display(SelectedTab.Value == tab); 34 | } 35 | toggle.SetupToolTip(tooltip, placementMode: PlacementMode.Left, verticalOffset: 0, horizontalOffset: -8, showDelay: 500); 36 | toggle.Switched.Subscribe(() => SelectedTab.Value = toggle.IsChecked ? tab : SideBarTab.None); 37 | SelectedTab.Modified.Subscribe(OnTabChanged); 38 | Content.Children.Add(toggle); 39 | Content.Children.Add(new Border() { Height = 1, Background = Style.BACK.ToBrush() }); 40 | OnTabChanged(); 41 | } 42 | 43 | AddTab(SideBarTab.Extensions, "Extensions".Tr(this), Assets.Extensions); 44 | AddTab(SideBarTab.Properties, "Properties".Tr(this), Assets.Properties); 45 | AddTab(SideBarTab.Export, "Export".Tr(this), Assets.Export); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /TuneLab/UI/MainWindow/MainWindow.axaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | #272736 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /TuneLab/UI/Settings/SettingsWindow.axaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /TuneLab/Utils/FileLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TuneLab.Base.Utils; 8 | 9 | namespace TuneLab.Utils; 10 | 11 | internal class FileLogger : ILogger 12 | { 13 | public FileLogger(string path) 14 | { 15 | PathManager.MakeSureExist(Path.GetDirectoryName(path)!); 16 | mStreamWriter = new StreamWriter(path); 17 | } 18 | 19 | ~FileLogger() 20 | { 21 | mStreamWriter.Close(); 22 | } 23 | 24 | public void WriteLine(string message) 25 | { 26 | System.Diagnostics.Debug.WriteLine(message); 27 | Console.WriteLine(message); 28 | mStreamWriter.WriteLine(message); 29 | mStreamWriter.Flush(); 30 | } 31 | 32 | readonly StreamWriter mStreamWriter; 33 | } 34 | -------------------------------------------------------------------------------- /TuneLab/Utils/IDGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TuneLab.Utils; 8 | 9 | internal class IDGenerator 10 | { 11 | public long GenerateID() 12 | { 13 | return ++id; 14 | } 15 | 16 | long id = 0; 17 | } 18 | -------------------------------------------------------------------------------- /TuneLab/Utils/LockFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Utils; 9 | 10 | internal class LockFile : IDisposable 11 | { 12 | public static LockFile? Create(string path) 13 | { 14 | try 15 | { 16 | PathManager.MakeSureExist(Path.GetDirectoryName(path)!); 17 | return new LockFile(File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)); 18 | } 19 | catch 20 | { 21 | return null; 22 | } 23 | } 24 | 25 | public void Dispose() 26 | { 27 | var path = mFileStream.Name; 28 | mFileStream.Close(); 29 | mFileStream.Dispose(); 30 | File.Delete(path); 31 | } 32 | 33 | LockFile(FileStream fileStream) 34 | { 35 | mFileStream = fileStream; 36 | } 37 | 38 | FileStream mFileStream; 39 | } 40 | -------------------------------------------------------------------------------- /TuneLab/Utils/ObjectPoolManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.ObjectPool; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TuneLab.Utils; 9 | 10 | internal static class ObjectPoolManager 11 | { 12 | public static ObjectPool GetObjectPool() where T : class, new() 13 | { 14 | if (!mObjectPools.TryGetValue(typeof(T), out var value)) 15 | { 16 | value = new DefaultObjectPool(new DefaultPooledObjectPolicy()); 17 | mObjectPools.Add(typeof(T), value); 18 | } 19 | 20 | return (ObjectPool)value; 21 | } 22 | 23 | public static T Get() where T : class, new() 24 | { 25 | return GetObjectPool().Get(); 26 | } 27 | 28 | public static void Return(T t) where T : class, new() 29 | { 30 | GetObjectPool().Return(t); 31 | } 32 | 33 | static Dictionary mObjectPools = new(); 34 | } 35 | -------------------------------------------------------------------------------- /TuneLab/Utils/PlatformHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace TuneLab.Utils 4 | { 5 | internal class PlatformHelper 6 | { 7 | public static string GetPlatform() 8 | { 9 | return $"{GetOS()}-{GetArchitecture()}"; 10 | } 11 | 12 | public static string GetOS() 13 | { 14 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 15 | return "win"; 16 | 17 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 18 | return "osx"; 19 | 20 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 21 | return "linux"; 22 | 23 | return "unknown"; 24 | } 25 | 26 | public static string GetArchitecture() 27 | { 28 | return RuntimeInformation.ProcessArchitecture switch 29 | { 30 | Architecture.X86 => "x86", 31 | Architecture.X64 => "x64", 32 | Architecture.Arm => "arm", 33 | Architecture.Arm64 => "arm64", 34 | _ => "unknown" 35 | }; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TuneLab/Utils/ProcessHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TuneLab.Utils; 10 | 11 | internal static class ProcessHelper 12 | { 13 | public static Process CreateProcess(string applicationPath, IEnumerable args) 14 | { 15 | var commandLine = string.Empty; 16 | foreach (var arg in args) 17 | { 18 | commandLine += "\"" + arg + "\" "; 19 | } 20 | 21 | Process process = new Process() 22 | { 23 | StartInfo = new ProcessStartInfo() 24 | { 25 | UseShellExecute = true, 26 | FileName = applicationPath, 27 | Arguments = commandLine, 28 | } 29 | }; 30 | process.Start(); 31 | 32 | return process; 33 | } 34 | 35 | // OpenUrl 36 | public static void OpenUrl(string url) 37 | { 38 | Process.Start(new ProcessStartInfo 39 | { 40 | FileName = url, 41 | UseShellExecute = true 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TuneLab/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------