diff --git a/src/SoundMaker/Sounds/Score/BasicSoundComponentBase.cs b/src/SoundMaker/Sounds/Score/BasicSoundComponentBase.cs
index 785645e..7d786b3 100644
--- a/src/SoundMaker/Sounds/Score/BasicSoundComponentBase.cs
+++ b/src/SoundMaker/Sounds/Score/BasicSoundComponentBase.cs
@@ -31,6 +31,8 @@ public BasicSoundComponentBase(LengthType length, bool isDotted)
public abstract short[] GenerateWave(SoundFormat format, int tempo, WaveTypeBase waveType);
+ public abstract ISoundComponent Clone();
+
public int GetWaveArrayLength(SoundFormat format, int tempo)
{
return SoundWaveLengthCalclator.Calclate(format, tempo, Length, IsDotted);
diff --git a/src/SoundMaker/Sounds/Score/ISoundComponent.cs b/src/SoundMaker/Sounds/Score/ISoundComponent.cs
index bb576bd..da7962a 100644
--- a/src/SoundMaker/Sounds/Score/ISoundComponent.cs
+++ b/src/SoundMaker/Sounds/Score/ISoundComponent.cs
@@ -37,4 +37,14 @@ public interface ISoundComponent
/// data of wave. 波形データ : short[]
/// Tempo must be non-negative and greater than 0.
short[] GenerateWave(SoundFormat format, int tempo, WaveTypeBase waveType);
+
+ ///
+ /// Creates a clone of the sound component.
+ /// サウンドコンポーネントのクローンを作成するメソッド。
+ ///
+ ///
+ /// A new instance of the sound component with the same properties.
+ /// 同じプロパティを持つサウンドコンポーネントの新しいインスタンス
+ ///
+ ISoundComponent Clone();
}
diff --git a/src/SoundMaker/Sounds/Score/Note.cs b/src/SoundMaker/Sounds/Score/Note.cs
index aa9815c..175f064 100644
--- a/src/SoundMaker/Sounds/Score/Note.cs
+++ b/src/SoundMaker/Sounds/Score/Note.cs
@@ -92,4 +92,12 @@ public override short[] GenerateWave(SoundFormat format, int tempo, WaveTypeBase
var length = GetWaveArrayLength(format, tempo);
return GenerateWave(format, tempo, length, waveType);
}
+
+ public override Note Clone()
+ {
+ return new(Scale, ScaleNumber, Length, IsDotted)
+ {
+ Volume = Volume,
+ };
+ }
}
diff --git a/src/SoundMaker/Sounds/Score/Rest.cs b/src/SoundMaker/Sounds/Score/Rest.cs
index 3af7480..f989083 100644
--- a/src/SoundMaker/Sounds/Score/Rest.cs
+++ b/src/SoundMaker/Sounds/Score/Rest.cs
@@ -4,15 +4,14 @@ namespace SoundMaker.Sounds.Score;
///
/// the rest. 休符を表すクラス
///
-public class Rest : BasicSoundComponentBase
+/// length (ex. "quarter" note) 長さ(音楽的な、「四分」音符、「全」休符のような長さを表す。)
+/// is note/rest dotted. 付点かを表す論理型
+public class Rest(LengthType length, bool isDotted = false) : BasicSoundComponentBase(length, isDotted)
{
- ///
- /// constructor コンストラクタ
- ///
- /// length (ex. "quarter" note) 長さ(音楽的な、「四分」音符、「全」休符のような長さを表す。)
- /// is note/rest dotted. 付点かを表す論理型
- public Rest(LengthType length, bool isDotted = false)
- : base(length, isDotted) { }
+ public override Rest Clone()
+ {
+ return new(Length, IsDotted);
+ }
public override short[] GenerateWave(SoundFormat format, int tempo, int length, WaveTypeBase waveType)
{
diff --git a/src/SoundMaker/Sounds/Score/Tie.cs b/src/SoundMaker/Sounds/Score/Tie.cs
index 9d3d7e4..3a21c3f 100644
--- a/src/SoundMaker/Sounds/Score/Tie.cs
+++ b/src/SoundMaker/Sounds/Score/Tie.cs
@@ -67,4 +67,22 @@ public int GetWaveArrayLength(SoundFormat format, int tempo)
}
return length;
}
+
+ ///
+ /// Creates a clone of the tie.
+ /// タイのクローンを作成するメソッド。
+ ///
+ /// A new instance of the tie with the same properties.
+ /// 同じプロパティを持つタイの新しいインスタンス
+ ///
+ public Tie Clone()
+ {
+ var newTie = new Tie(BaseNote.Clone(), AdditionalNotes.Select(note => note.Clone()).ToArray());
+ return newTie;
+ }
+
+ ISoundComponent ISoundComponent.Clone()
+ {
+ return Clone();
+ }
}
diff --git a/src/SoundMaker/Sounds/Score/Tuplet.cs b/src/SoundMaker/Sounds/Score/Tuplet.cs
index d7e45b5..18f2a68 100644
--- a/src/SoundMaker/Sounds/Score/Tuplet.cs
+++ b/src/SoundMaker/Sounds/Score/Tuplet.cs
@@ -97,4 +97,22 @@ private int GetLengthPerOneComponent()
}
return count;
}
+
+ ///
+ /// Creates a clone of the tuplet.
+ /// 連符のクローンを作成するメソッド。
+ ///
+ /// A new instance of the tuplet with the same properties.
+ /// 同じプロパティを持つ連符の新しいインスタンス
+ ///
+ public Tuplet Clone()
+ {
+ var cloned = new Tuplet(TupletComponents.Select(component => component.Clone()).ToArray(), Length, IsDotted);
+ return cloned;
+ }
+
+ ISoundComponent ISoundComponent.Clone()
+ {
+ return Clone();
+ }
}
diff --git a/src/SoundMaker/Sounds/SoundChannels/SoundChannelBase.cs b/src/SoundMaker/Sounds/SoundChannels/SoundChannelBase.cs
index 0dbbd8b..78ad536 100644
--- a/src/SoundMaker/Sounds/SoundChannels/SoundChannelBase.cs
+++ b/src/SoundMaker/Sounds/SoundChannels/SoundChannelBase.cs
@@ -47,7 +47,7 @@ public SoundChannelBase(int tempo, SoundFormat format, PanType panType)
Format = format;
if (tempo <= 0)
{
- throw new ArgumentOutOfRangeException("'tempo' must be non-negative and greater than 0.");
+ throw new ArgumentOutOfRangeException(nameof(tempo), "'tempo' must be non-negative and greater than 0.");
}
Tempo = tempo;
}
@@ -55,7 +55,7 @@ public SoundChannelBase(int tempo, SoundFormat format, PanType panType)
///
/// サウンドコンポーネントのリスト
///
- protected List SoundComponents { get; private set; } = new List();
+ protected List SoundComponents { get; private set; } = [];
public SoundFormat Format { get; }
@@ -100,7 +100,7 @@ public void RemoveAt(int index)
{
if (SoundComponents.Count <= index || index < 0)
{
- throw new ArgumentOutOfRangeException("index is less than 0 or index is equal to or greater than ComponentCount.");
+ throw new ArgumentOutOfRangeException(nameof(index), "index is less than 0 or index is equal to or greater than ComponentCount.");
}
var component = SoundComponents[index];
WaveArrayLength -= component.GetWaveArrayLength(Format, Tempo);
diff --git a/src/SoundMaker/Sounds/Track.cs b/src/SoundMaker/Sounds/Track.cs
new file mode 100644
index 0000000..7aed704
--- /dev/null
+++ b/src/SoundMaker/Sounds/Track.cs
@@ -0,0 +1,260 @@
+using SoundMaker.Sounds.Score;
+using SoundMaker.Sounds.WaveTypes;
+
+namespace SoundMaker.Sounds;
+
+///
+/// Represents a track with a specific wave type.
+/// 特定の波形タイプを持つトラックを表すクラス。
+///
+public class Track
+{
+ private List _soundComponents = [];
+
+ private readonly SoundFormat _format;
+
+ private readonly int _tempo;
+
+ ///
+ /// Initializes a new instance of the Track class.
+ /// Track クラスの新しいインスタンスを初期化するコンストラクタ。
+ ///
+ /// The wave type.
波形タイプ。
+ /// The sound format.
サウンドフォーマット。
+ /// The tempo.
テンポ。
+ /// The start time in milliseconds.
開始時間(ミリ秒)。
+ internal Track(WaveTypeBase waveType, SoundFormat format, int tempo, int startMilliSecond)
+ {
+ WaveType = waveType;
+ _format = format;
+ _tempo = tempo;
+ StartMilliSecond = startMilliSecond;
+ }
+
+
+ ///
+ /// Sound components
+ /// サウンドコンポーネント
+ ///
+ public IReadOnlyList SoundComponents => _soundComponents;
+
+ private double _pan = 0;
+ ///
+ /// 左右の音量バランスを取得または設定するプロパティ。
+ /// -1.0が左、1.0が右側。
+ /// Gets or sets the left-right audio balance.
+ /// Takes values from -1.0 (left) to 1.0 (right).
+ ///
+ public double Pan
+ {
+ get => _pan;
+ set
+ {
+ value = value > 1.0 ? 1.0 : value;
+ value = value < -1.0 ? -1.0 : value;
+
+ _pan = value;
+ }
+ }
+
+ internal int EndIndex { get; private set; }
+ internal int StartIndex { get; private set; }
+ private int _startMilliSecond;
+ ///
+ /// Gets or sets the start time in milliseconds.
+ /// 開始時間(ミリ秒)を取得または設定するプロパティ。
+ ///
+ internal int StartMilliSecond
+ {
+ get => _startMilliSecond;
+ set
+ {
+ // 負の数は許可しない
+ if (value < 0)
+ {
+ value = 0;
+ }
+
+ _startMilliSecond = value;
+
+ // 開始ミリ秒が変わると開始時、終了時のインデクスも変わるので、再計算する
+ var samplingFrequencyMS = (int)_format.SamplingFrequency / 1000.0;
+ StartIndex = (int)(StartMilliSecond * samplingFrequencyMS);
+ if (WaveArrayLength == 0)
+ {
+ EndIndex = StartIndex;
+ }
+ else
+ {
+ EndIndex = StartIndex + WaveArrayLength - 1;
+ }
+ }
+ }
+
+ private int _waveArrayLength;
+ ///
+ /// Gets the length of the wave array.
+ /// 波形配列の長さを取得するプロパティ。
+ ///
+ public int WaveArrayLength
+ {
+ get => _waveArrayLength;
+ set
+ {
+ _waveArrayLength = value;
+
+ // 配列の長さが変わると終了時インデクスが変わるので、再計算する
+ if (WaveArrayLength == 0)
+ {
+ EndIndex = StartIndex;
+ }
+ else
+ {
+ EndIndex = StartIndex + WaveArrayLength - 1;
+ }
+ }
+ }
+
+ ///
+ /// Count of sound components.
+ /// サウンドコンポーネントの数
+ ///
+ public int Count => _soundComponents.Count;
+
+ ///
+ /// Gets or sets the wave type.
+ /// 波形タイプを取得または設定するプロパティ。
+ ///
+ public WaveTypeBase WaveType { get; set; }
+
+ ///
+ /// Generates a wave based on the sound components.
+ /// サウンドコンポーネントに基づいて波形を生成するメソッド。
+ ///
+ ///
+ /// An array of shorts representing the generated wave.
+ /// 生成された波形を表すショート型の配列。
+ ///
+ public short[] GenerateWave()
+ {
+ var result = new List();
+ foreach (var soundComponent in _soundComponents)
+ {
+ var wave = soundComponent.GenerateWave(_format, _tempo, WaveType);
+ result.AddRange(wave);
+ }
+
+ return [.. result];
+ }
+
+
+ ///
+ /// Adds a sound component to the track.
+ /// トラックにサウンドコンポーネントを追加するメソッド。
+ ///
+ /// The sound component to add.
追加するサウンドコンポーネント。
+ public void Add(ISoundComponent component)
+ {
+ WaveArrayLength += component.GetWaveArrayLength(_format, _tempo);
+ _soundComponents.Add(component);
+ }
+
+ ///
+ /// Inserts a sound component at the specified index.
+ /// 指定されたインデックスにサウンドコンポーネントを挿入するメソッド。
+ ///
+ /// The zero-based index at which the component should be inserted.
+ /// コンポーネントを挿入するゼロベースのインデックス。
+ /// The sound component to insert.
+ /// 挿入するサウンドコンポーネント。
+ /// Thrown when the index is out of range.
+ /// インデックスが範囲外の場合にスローされる例外。
+
+ public void Insert(int index, ISoundComponent component)
+ {
+ if (IsOutOfRange(index))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ _soundComponents.Insert(index, component);
+ WaveArrayLength += component.GetWaveArrayLength(_format, _tempo);
+ }
+
+ ///
+ /// Removes a sound component at the specified index.
+ /// 指定されたインデックスのサウンドコンポーネントを削除するメソッド。
+ ///
+ /// The index of the sound component to remove.
削除するサウンドコンポーネントのインデックス。
+ /// Thrown when the index is out of range.
インデックスが範囲外の場合にスローされる例外。
+ public void RemoveAt(int index)
+ {
+ if (IsOutOfRange(index))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ var targetComponent = _soundComponents[index];
+ _soundComponents.Remove(targetComponent);
+ WaveArrayLength -= targetComponent.GetWaveArrayLength(_format, _tempo);
+ }
+
+ ///
+ /// Removes a specified sound component.
+ /// 指定されたサウンドコンポーネントを削除するメソッド。
+ ///
+ /// The sound component to remove.
削除するサウンドコンポーネント。
+ /// True if the component was removed; otherwise, false.
コンポーネントが削除された場合は true、それ以外の場合は false。
+ public bool Remove(ISoundComponent component)
+ {
+ var ok = _soundComponents.Remove(component);
+ if (ok)
+ {
+ WaveArrayLength -= component.GetWaveArrayLength(_format, _tempo);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Clears all sound components from the track.
+ /// トラックからすべてのサウンドコンポーネントをクリアするメソッド。
+ ///
+ public void Clear()
+ {
+ WaveArrayLength = 0;
+ _soundComponents.Clear();
+ }
+
+ ///
+ /// Imports a collection of sound components into the track.
+ /// トラックにサウンドコンポーネントのコレクションをインポートするメソッド。
+ ///
+ /// The collection of sound components to import.
インポートするサウンドコンポーネントのコレクション。
+ public void Import(IEnumerable components)
+ {
+ _soundComponents = new List(components);
+ WaveArrayLength = components.Sum(component => component.GetWaveArrayLength(_format, _tempo));
+ }
+
+ ///
+ /// Creates a clone of the track.
+ /// トラックのクローンを作成するメソッド。
+ ///
+ /// A new instance of the track with the same properties.
同じプロパティを持つトラックの新しいインスタンス。
+ internal Track Clone()
+ {
+ var copy = new Track(WaveType.Clone(), _format, _tempo, StartMilliSecond)
+ {
+ WaveArrayLength = WaveArrayLength,
+ _soundComponents = _soundComponents.Select(component => component.Clone()).ToList()
+ };
+ return copy;
+ }
+
+ private bool IsOutOfRange(int index)
+ {
+ return index < 0 || index >= _soundComponents.Count;
+ }
+}
diff --git a/src/SoundMaker/Sounds/TrackBaseSound.cs b/src/SoundMaker/Sounds/TrackBaseSound.cs
new file mode 100644
index 0000000..c371d06
--- /dev/null
+++ b/src/SoundMaker/Sounds/TrackBaseSound.cs
@@ -0,0 +1,336 @@
+using SoundMaker.Sounds.WaveTypes;
+
+namespace SoundMaker.Sounds;
+
+///
+/// Initializes a new instance of the TrackBaseSound class with the specified format and tempo.
+/// 指定されたフォーマットとテンポでTrackBaseSoundクラスの新しいインスタンスを初期化するメソッド。
+///
+/// The sound format to be used.
+/// 使用するサウンドフォーマット。
+///
+/// The tempo of the track.
+/// トラックのテンポ。
+///
+public class TrackBaseSound(SoundFormat format, int tempo)
+{
+ ///
+ /// Gets the tempo value.
+ /// テンポの値を取得するプロパティ。
+ ///
+ public int Tempo { get; } = tempo;
+
+ ///
+ /// Gets the sound format.
+ /// サウンドフォーマットを取得するプロパティ。
+ ///
+ public SoundFormat Format { get; } = format;
+
+ ///
+ /// トラックを管理する辞書
+ /// 開始時間(ミリ秒)とトラック(複数)のペア
+ ///
+ private Dictionary> _tracksTimeMap = [];
+
+ ///
+ /// Creates a new track with the specified wave type and start time.
+ /// 指定された波の種類と開始時間で新しいトラックを作成するメソッド。
+ ///
+ /// The start time in milliseconds.
開始時間(ミリ秒)。
+ /// The type of wave.
波の種類。
+ /// A new instance of the track.
新しいトラックのインスタンス。
+ public Track CreateTrack(int startMilliSecond, WaveTypeBase waveType)
+ {
+ var track = new Track(waveType, Format, Tempo, startMilliSecond);
+ InsertTrack(startMilliSecond, track);
+ return track;
+ }
+
+ ///
+ /// Removes all tracks at the specified start time.
+ /// 指定された開始時間のすべてのトラックを削除するメソッド。
+ ///
+ /// The start time in milliseconds.
開始時間(ミリ秒)。
+ /// True if tracks were removed; otherwise, false.
トラックが削除された場合は true、それ以外の場合は false。
+ public bool RemoveTracksAt(int startMilliSecond)
+ {
+ return _tracksTimeMap.Remove(startMilliSecond);
+ }
+
+ ///
+ /// Removes the specified track.
+ /// 指定されたトラックを削除するメソッド。
+ ///
+ /// The track to remove.
削除するトラック。
+ /// True if the track was removed; otherwise, false.
トラックが削除された場合は true、それ以外の場合は false。
+ public bool RemoveTrack(Track track)
+ {
+ if (_tracksTimeMap.TryGetValue(track.StartMilliSecond, out var tracks))
+ {
+ var ok = tracks.Remove(track);
+ if (!ok)
+ {
+ return false;
+ }
+
+ if (tracks.Count == 0)
+ {
+ return _tracksTimeMap.Remove(track.StartMilliSecond);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the list of tracks at the specified start time.
+ /// 指定された開始時間のトラックのリストを取得するメソッド。
+ ///
+ /// The start time in milliseconds.
開始時間(ミリ秒)。
+ ///
+ /// A list of tracks.
+ /// トラックのリスト。
+ /// If the operation fails, an empty list is returned.
+ /// 失敗時は空リスト。
+ ///
+
+ public List