Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#20 Provide a new API to create complex sounds #34

Merged
merged 19 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/SoundMaker/Sounds/Score/BasicSoundComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions src/SoundMaker/Sounds/Score/ISoundComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,14 @@ public interface ISoundComponent
/// <returns>data of wave. 波形データ : short[]</returns>
/// <exception cref="ArgumentOutOfRangeException">Tempo must be non-negative and greater than 0.</exception>
short[] GenerateWave(SoundFormat format, int tempo, WaveTypeBase waveType);

/// <summary>
/// Creates a clone of the sound component. <br/>
/// サウンドコンポーネントのクローンを作成するメソッド。
/// </summary>
/// <returns>
/// A new instance of the sound component with the same properties. <br/>
/// 同じプロパティを持つサウンドコンポーネントの新しいインスタンス
/// </returns>
ISoundComponent Clone();
}
8 changes: 8 additions & 0 deletions src/SoundMaker/Sounds/Score/Note.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
}
15 changes: 7 additions & 8 deletions src/SoundMaker/Sounds/Score/Rest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ namespace SoundMaker.Sounds.Score;
/// <summary>
/// the rest. 休符を表すクラス
/// </summary>
public class Rest : BasicSoundComponentBase
/// <param name="length">length (ex. "quarter" note) 長さ(音楽的な、「四分」音符、「全」休符のような長さを表す。)</param>
/// <param name="isDotted">is note/rest dotted. 付点かを表す論理型</param>
public class Rest(LengthType length, bool isDotted = false) : BasicSoundComponentBase(length, isDotted)
{
/// <summary>
/// constructor コンストラクタ
/// </summary>
/// <param name="length">length (ex. "quarter" note) 長さ(音楽的な、「四分」音符、「全」休符のような長さを表す。)</param>
/// <param name="isDotted">is note/rest dotted. 付点かを表す論理型</param>
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)
{
Expand Down
18 changes: 18 additions & 0 deletions src/SoundMaker/Sounds/Score/Tie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,22 @@ public int GetWaveArrayLength(SoundFormat format, int tempo)
}
return length;
}

/// <summary>
/// Creates a clone of the tie. <br/>
/// タイのクローンを作成するメソッド。
/// </summary>
/// <returns>A new instance of the tie with the same properties. <br/>
/// 同じプロパティを持つタイの新しいインスタンス
/// </returns>
public Tie Clone()
{
var newTie = new Tie(BaseNote.Clone(), AdditionalNotes.Select(note => note.Clone()).ToArray());
return newTie;
}

ISoundComponent ISoundComponent.Clone()
{
return Clone();
}
}
18 changes: 18 additions & 0 deletions src/SoundMaker/Sounds/Score/Tuplet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,22 @@ private int GetLengthPerOneComponent()
}
return count;
}

/// <summary>
/// Creates a clone of the tuplet. <br/>
/// 連符のクローンを作成するメソッド。
/// </summary>
/// <returns>A new instance of the tuplet with the same properties. <br/>
/// 同じプロパティを持つ連符の新しいインスタンス
/// </returns>
public Tuplet Clone()
{
var cloned = new Tuplet(TupletComponents.Select(component => component.Clone()).ToArray(), Length, IsDotted);
return cloned;
}

ISoundComponent ISoundComponent.Clone()
{
return Clone();
}
}
6 changes: 3 additions & 3 deletions src/SoundMaker/Sounds/SoundChannels/SoundChannelBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ 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;
}

/// <summary>
/// サウンドコンポーネントのリスト
/// </summary>
protected List<ISoundComponent> SoundComponents { get; private set; } = new List<ISoundComponent>();
protected List<ISoundComponent> SoundComponents { get; private set; } = [];

public SoundFormat Format { get; }

Expand Down Expand Up @@ -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);
Expand Down
260 changes: 260 additions & 0 deletions src/SoundMaker/Sounds/Track.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
using SoundMaker.Sounds.Score;
using SoundMaker.Sounds.WaveTypes;

namespace SoundMaker.Sounds;

/// <summary>
/// Represents a track with a specific wave type. <br/>
/// 特定の波形タイプを持つトラックを表すクラス。
/// </summary>
public class Track
{
private List<ISoundComponent> _soundComponents = [];

private readonly SoundFormat _format;

private readonly int _tempo;

/// <summary>
/// Initializes a new instance of the Track class. <br/>
/// Track クラスの新しいインスタンスを初期化するコンストラクタ。
/// </summary>
/// <param name="waveType">The wave type. <br/> 波形タイプ。</param>
/// <param name="format">The sound format. <br/> サウンドフォーマット。</param>
/// <param name="tempo">The tempo. <br/> テンポ。</param>
/// <param name="startMilliSecond">The start time in milliseconds. <br/> 開始時間(ミリ秒)。</param>
internal Track(WaveTypeBase waveType, SoundFormat format, int tempo, int startMilliSecond)
{
WaveType = waveType;
_format = format;
_tempo = tempo;
StartMilliSecond = startMilliSecond;
}


/// <summary>
/// Sound components<br/>
/// サウンドコンポーネント
/// </summary>
public IReadOnlyList<ISoundComponent> SoundComponents => _soundComponents;

private double _pan = 0;
/// <summary>
/// 左右の音量バランスを取得または設定するプロパティ。<br/>
/// -1.0が左、1.0が右側。<br/>
/// Gets or sets the left-right audio balance. <br/>
/// Takes values from -1.0 (left) to 1.0 (right).
/// </summary>
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;
/// <summary>
/// Gets or sets the start time in milliseconds. <br/>
/// 開始時間(ミリ秒)を取得または設定するプロパティ。
/// </summary>
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;
/// <summary>
/// Gets the length of the wave array. <br/>
/// 波形配列の長さを取得するプロパティ。
/// </summary>
public int WaveArrayLength
{
get => _waveArrayLength;
set
{
_waveArrayLength = value;

// 配列の長さが変わると終了時インデクスが変わるので、再計算する
if (WaveArrayLength == 0)
{
EndIndex = StartIndex;
}
else
{
EndIndex = StartIndex + WaveArrayLength - 1;
}
}
}

/// <summary>
/// Count of sound components. <br/>
/// サウンドコンポーネントの数
/// </summary>
public int Count => _soundComponents.Count;

/// <summary>
/// Gets or sets the wave type. <br/>
/// 波形タイプを取得または設定するプロパティ。
/// </summary>
public WaveTypeBase WaveType { get; set; }

/// <summary>
/// Generates a wave based on the sound components. <br/>
/// サウンドコンポーネントに基づいて波形を生成するメソッド。
/// </summary>
/// <returns>
/// An array of shorts representing the generated wave. <br/>
/// 生成された波形を表すショート型の配列。
/// </returns>
public short[] GenerateWave()
{
var result = new List<short>();
foreach (var soundComponent in _soundComponents)
{
var wave = soundComponent.GenerateWave(_format, _tempo, WaveType);
result.AddRange(wave);
}

return [.. result];
}


/// <summary>
/// Adds a sound component to the track. <br/>
/// トラックにサウンドコンポーネントを追加するメソッド。
/// </summary>
/// <param name="component">The sound component to add. <br/> 追加するサウンドコンポーネント。</param>
public void Add(ISoundComponent component)
{
WaveArrayLength += component.GetWaveArrayLength(_format, _tempo);
_soundComponents.Add(component);
}

/// <summary>
/// Inserts a sound component at the specified index. <br/>
/// 指定されたインデックスにサウンドコンポーネントを挿入するメソッド。
/// </summary>
/// <param name="index">The zero-based index at which the component should be inserted. <br/>
/// コンポーネントを挿入するゼロベースのインデックス。</param>
/// <param name="component">The sound component to insert. <br/>
/// 挿入するサウンドコンポーネント。</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range. <br/>
/// インデックスが範囲外の場合にスローされる例外。</exception>

public void Insert(int index, ISoundComponent component)
{
if (IsOutOfRange(index))
{
throw new ArgumentOutOfRangeException(nameof(index));
}

_soundComponents.Insert(index, component);
WaveArrayLength += component.GetWaveArrayLength(_format, _tempo);
}

/// <summary>
/// Removes a sound component at the specified index. <br/>
/// 指定されたインデックスのサウンドコンポーネントを削除するメソッド。
/// </summary>
/// <param name="index">The index of the sound component to remove. <br/> 削除するサウンドコンポーネントのインデックス。</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range. <br/> インデックスが範囲外の場合にスローされる例外。</exception>
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);
}

/// <summary>
/// Removes a specified sound component. <br/>
/// 指定されたサウンドコンポーネントを削除するメソッド。
/// </summary>
/// <param name="component">The sound component to remove. <br/> 削除するサウンドコンポーネント。</param>
/// <returns>True if the component was removed; otherwise, false. <br/> コンポーネントが削除された場合は true、それ以外の場合は false。</returns>
public bool Remove(ISoundComponent component)
{
var ok = _soundComponents.Remove(component);
if (ok)
{
WaveArrayLength -= component.GetWaveArrayLength(_format, _tempo);
return true;
}

return false;
}

/// <summary>
/// Clears all sound components from the track. <br/>
/// トラックからすべてのサウンドコンポーネントをクリアするメソッド。
/// </summary>
public void Clear()
{
WaveArrayLength = 0;
_soundComponents.Clear();
}

/// <summary>
/// Imports a collection of sound components into the track. <br/>
/// トラックにサウンドコンポーネントのコレクションをインポートするメソッド。
/// </summary>
/// <param name="components">The collection of sound components to import. <br/> インポートするサウンドコンポーネントのコレクション。</param>
public void Import(IEnumerable<ISoundComponent> components)
{
_soundComponents = new List<ISoundComponent>(components);
WaveArrayLength = components.Sum(component => component.GetWaveArrayLength(_format, _tempo));
}

/// <summary>
/// Creates a clone of the track. <br/>
/// トラックのクローンを作成するメソッド。
/// </summary>
/// <returns>A new instance of the track with the same properties. <br/> 同じプロパティを持つトラックの新しいインスタンス。</returns>
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;
}
}
Loading
Loading