├── .config
└── dotnet-tools.json
├── .editorconfig
├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── Content
├── Content.mgcb
├── OpenSans-SemiBold.ttf
├── WaveTracker.ttf
├── high_resolution_font.ttf
├── img.png
├── wavetracker_font.spritefont
└── wt_logo.ico
├── Icon.ico
├── LICENSE
├── README.md
├── Source
├── App.cs
├── Audio
│ ├── AudioEngine.cs
│ ├── Channel.cs
│ ├── ChannelManager.cs
│ ├── EnvelopePlayer.cs
│ ├── StereoBiQuadFilter.cs
│ └── TickEvent.cs
├── Helpers.cs
├── Input
│ ├── Input.cs
│ ├── KeyboardShortcut.cs
│ ├── PianoInput.cs
│ ├── SystemClipboard.cs
│ └── Tooltip.cs
├── Program.cs
├── Rendering
│ ├── Colors.cs
│ ├── Graphics.cs
│ └── HSLColor.cs
├── SettingsProfile.cs
├── Tracker
│ ├── CursorPos.cs
│ ├── Envelope.cs
│ ├── Instrument.cs
│ ├── NoiseInstrument.cs
│ ├── PatternEditorPosition.cs
│ ├── PatternEditorState.cs
│ ├── PatternSelection.cs
│ ├── Playback.cs
│ ├── Sample.cs
│ ├── SampleInstrument.cs
│ ├── SaveLoad.cs
│ ├── WTFrame.cs
│ ├── WTModule.cs
│ ├── WTPattern.cs
│ ├── WTSong.cs
│ ├── Wave.cs
│ └── WaveInstrument.cs
└── UI
│ ├── Button.cs
│ ├── ButtonColors.cs
│ ├── ChannelHeader.cs
│ ├── Checkbox.cs
│ ├── CheckboxLabeled.cs
│ ├── Clickable.cs
│ ├── ColorButton.cs
│ ├── ColorButtonList.cs
│ ├── ColorPickerDialog.cs
│ ├── ConfigurationDialog.cs
│ ├── ContextMenu.cs
│ ├── Dialog.cs
│ ├── Dialogs.cs
│ ├── Dropdown.cs
│ ├── DropdownButton.cs
│ ├── EditSettings.cs
│ ├── Element.cs
│ ├── EnvelopeEditor.cs
│ ├── EnvelopeListBox.cs
│ ├── EnvelopeListItem.cs
│ ├── ExportDialog.cs
│ ├── ExportingDialog.cs
│ ├── FrameButton.cs
│ ├── FramesPanel.cs
│ ├── HorizontalSlider.cs
│ ├── HumanizeDialog.cs
│ ├── InputField.cs
│ ├── InstrumentBank.cs
│ ├── InstrumentEditor.cs
│ ├── KeyboardBindingList.cs
│ ├── ListBox.cs
│ ├── Menu.cs
│ ├── MenuItemBase.cs
│ ├── MenuOption.cs
│ ├── MenuStrip.cs
│ ├── MessageDialog.cs
│ ├── ModulePanel.cs
│ ├── ModuleSettingsDialog.cs
│ ├── MouseRegion.cs
│ ├── NumberBox.cs
│ ├── NumberBoxDecimal.cs
│ ├── Panel.cs
│ ├── PatternEditor.cs
│ ├── PreviewPiano.cs
│ ├── SampleAmplifyDialog.cs
│ ├── SampleBitcrushDialog.cs
│ ├── SampleBrowser.cs
│ ├── SampleEditor.cs
│ ├── SampleModifyDialog.cs
│ ├── Scrollbar.cs
│ ├── ScrollbarHorizontal.cs
│ ├── SetFramePatternDialog.cs
│ ├── SpriteButton.cs
│ ├── SpriteToggle.cs
│ ├── SpriteToggleTwoSided.cs
│ ├── StretchDialog.cs
│ ├── SubMenu.cs
│ ├── SwitchToggle.cs
│ ├── SwitchToggleCustom.cs
│ ├── TabGroup.cs
│ ├── Textbox.cs
│ ├── Toggle.cs
│ ├── Toolbar.cs
│ ├── UISoundsManager.cs
│ ├── VerticalListSelector.cs
│ ├── Visualizer.cs
│ ├── WaveAddFuzzDialog.cs
│ ├── WaveBank.cs
│ ├── WaveBankElement.cs
│ ├── WaveEditor.cs
│ ├── WaveMathExpressionDialog.cs
│ ├── WaveModifyDialog.cs
│ ├── WaveSampleAndHoldDialog.cs
│ ├── WaveSmoothDialog.cs
│ ├── WaveSyncDialog.cs
│ └── Window.cs
├── WaveTracker.csproj
├── WaveTracker.sln
├── app.manifest
└── wavetracker.png
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "dotnet-mgcb": {
6 | "version": "3.8.1.303",
7 | "commands": [
8 | "mgcb"
9 | ]
10 | },
11 | "dotnet-mgcb-editor": {
12 | "version": "3.8.1.303",
13 | "commands": [
14 | "mgcb-editor"
15 | ]
16 | },
17 | "dotnet-mgcb-editor-linux": {
18 | "version": "3.8.1.303",
19 | "commands": [
20 | "mgcb-editor-linux"
21 | ]
22 | },
23 | "dotnet-mgcb-editor-windows": {
24 | "version": "3.8.1.303",
25 | "commands": [
26 | "mgcb-editor-windows"
27 | ]
28 | },
29 | "dotnet-mgcb-editor-mac": {
30 | "version": "3.8.1.303",
31 | "commands": [
32 | "mgcb-editor-mac"
33 | ]
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Report a bug in WaveTracker
4 | title: "[BUG]"
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS:
28 | - Version:
29 |
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for WaveTracker
4 | title: "[Feature Request]"
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/Content/Content.mgcb:
--------------------------------------------------------------------------------
1 |
2 | #----------------------------- Global Properties ----------------------------#
3 |
4 | /outputDir:bin/$(Platform)
5 | /intermediateDir:obj/$(Platform)
6 | /platform:DesktopGL
7 | /config:
8 | /profile:Reach
9 | /compress:False
10 |
11 | #-------------------------------- References --------------------------------#
12 |
13 |
14 | #---------------------------------- Content ---------------------------------#
15 |
16 | #begin high_resolution_font.ttf
17 | /copy:high_resolution_font.ttf
18 |
19 | #begin img.png
20 | /importer:TextureImporter
21 | /processor:TextureProcessor
22 | /processorParam:ColorKeyColor=255,0,255,255
23 | /processorParam:ColorKeyEnabled=True
24 | /processorParam:GenerateMipmaps=False
25 | /processorParam:PremultiplyAlpha=True
26 | /processorParam:ResizeToPowerOfTwo=False
27 | /processorParam:MakeSquare=False
28 | /processorParam:TextureFormat=Color
29 | /build:img.png
30 |
31 | #begin wavetracker_font.spritefont
32 | /importer:FontDescriptionImporter
33 | /processor:FontDescriptionProcessor
34 | /processorParam:PremultiplyAlpha=True
35 | /processorParam:TextureFormat=Compressed
36 | /build:wavetracker_font.spritefont
37 |
38 |
--------------------------------------------------------------------------------
/Content/OpenSans-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/squiggythings/WaveTracker/50f619586c185f52ad03d74b264144909d5166f5/Content/OpenSans-SemiBold.ttf
--------------------------------------------------------------------------------
/Content/WaveTracker.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/squiggythings/WaveTracker/50f619586c185f52ad03d74b264144909d5166f5/Content/WaveTracker.ttf
--------------------------------------------------------------------------------
/Content/high_resolution_font.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/squiggythings/WaveTracker/50f619586c185f52ad03d74b264144909d5166f5/Content/high_resolution_font.ttf
--------------------------------------------------------------------------------
/Content/img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/squiggythings/WaveTracker/50f619586c185f52ad03d74b264144909d5166f5/Content/img.png
--------------------------------------------------------------------------------
/Content/wavetracker_font.spritefont:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
14 | WaveTracker.ttf
15 |
16 |
20 | 8
21 |
22 |
26 | 0
27 |
28 |
32 | true
33 |
34 |
38 |
39 |
40 |
44 | □
45 |
46 |
53 |
54 |
55 |
56 | 翿
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Content/wt_logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/squiggythings/WaveTracker/50f619586c185f52ad03d74b264144909d5166f5/Content/wt_logo.ico
--------------------------------------------------------------------------------
/Icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/squiggythings/WaveTracker/50f619586c185f52ad03d74b264144909d5166f5/Icon.ico
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 squiggythings/Elias Ananiadis
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 | # WaveTracker
2 |
3 |
4 |
5 | \
6 | [WaveTracker](https://www.wavetracker.org) is a free and open source music-making software for Windows. It uses basic wavetable synthesis and sampling to generate sounds, with endless combinations of effects to warp, modify or distort waves and sounds.
7 |
8 | **If you are looking to download WaveTracker, visit the [itch.io](https://squiggygames.itch.io/wavetracker) page.**
9 |
10 | ### Features
11 |
12 | - Realtime playback and editing
13 | - Draw and edit up to 100 waveforms
14 | - Up to 100 instrument macros to control various aspects of the sound
15 | - Sample importing and editing
16 | - MIDI input
17 | - Export to .wav
18 | - Built in oscilloscope and piano roll visualizer
19 |
20 | For an in depth explanation of how to use WaveTracker, visit the [official documentation](https://www.wavetracker.org/documentation)
21 |
22 | WaveTracker and its source code are licensed under the MIT license.
23 |
24 | ### Links
25 |
26 | - [wavetracker.org](https://www.wavetracker.org)
27 | - [documentation](https://www.wavetracker.org/documentation)
28 | - [itch.io page](https://squiggygames.itch.io/wavetracker)
29 |
30 | ## Contributing
31 |
32 | First, thank you so much for wanting to contribute to WaveTracker! If you would like to contribute I suggest opening a feature request or getting in contact with me first so we can discuss and come up with a roadmap, this way we avoid wasting both our time.
33 |
34 | ## Contact
35 |
36 | If you would like to report a bug or suggest a feature please open an issue here or contact me:
37 | - Twitter: [@squiggythings](https://twitter.com/squiggythings)
38 | - YouTube: [@squiggythings](https://www.youtube.com/channel/UCrNoYf6XA4IHLf-1ZeqN81g?view_as=subscriber)
39 | - Itch.io: [squiggy](https://squiggygames.itch.io/wavetracker)
40 | - E-mail: squiggymakesmusic@gmail.com
41 |
--------------------------------------------------------------------------------
/Source/Audio/EnvelopePlayer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using WaveTracker.Tracker;
3 |
4 | namespace WaveTracker.Audio {
5 | public class EnvelopePlayer {
6 | public bool Enabled { get; set; }
7 | public int step;
8 | public bool released;
9 | public int Value { get; private set; }
10 | public Envelope EnvelopeToPlay { get; private set; }
11 | public bool EnvelopeEnded { get; private set; }
12 | public Envelope.EnvelopeType Type { get; private set; }
13 |
14 | public EnvelopePlayer(Envelope.EnvelopeType type) {
15 | step = 0;
16 | Type = type;
17 | released = false;
18 | EnvelopeToPlay = new Envelope(0);
19 | EnvelopeEnded = true;
20 | }
21 |
22 | public void SetEnvelope(Envelope envelope) {
23 | if (envelope == null) {
24 | EnvelopeToPlay = null;
25 | Evaluate();
26 | return;
27 | }
28 | if (envelope.Type != Type) {
29 | throw new ArgumentException("Envelope type and player type do not match!");
30 | }
31 | else {
32 | EnvelopeToPlay = envelope;
33 | Evaluate();
34 | }
35 | }
36 |
37 | public bool HasActiveEnvelopeData {
38 | get {
39 | return EnvelopeToPlay != null && EnvelopeToPlay.Length > 0 && EnvelopeToPlay.IsActive;
40 | }
41 | }
42 |
43 | public void Start() {
44 | step = 0;
45 | EnvelopeEnded = false;
46 | released = false;
47 | Evaluate();
48 | }
49 |
50 | public void Release() {
51 | released = true;
52 | if (EnvelopeToPlay.HasRelease) {
53 | step = EnvelopeToPlay.ReleaseIndex + 1;
54 | Evaluate();
55 | }
56 | }
57 |
58 | private int GetDefaultValue() {
59 | return Type switch {
60 | Envelope.EnvelopeType.Volume => 99,
61 | _ => 0,
62 | };
63 | }
64 |
65 | public void Step() {
66 | if (EnvelopeToPlay.IsActive && EnvelopeToPlay.Length > 0) {
67 | step++;
68 | if (EnvelopeToPlay.HasRelease) {
69 | if (step > EnvelopeToPlay.ReleaseIndex && !released) {
70 | if (EnvelopeToPlay.ReleaseIndex <= EnvelopeToPlay.LoopIndex || !EnvelopeToPlay.HasLoop) {
71 | step = EnvelopeToPlay.ReleaseIndex;
72 | }
73 | }
74 | if (EnvelopeToPlay.HasLoop) {
75 | if (EnvelopeToPlay.ReleaseIndex >= EnvelopeToPlay.LoopIndex) {
76 | if (step > EnvelopeToPlay.ReleaseIndex && !released) {
77 | step = EnvelopeToPlay.LoopIndex;
78 | }
79 | }
80 | else {
81 | if (step >= EnvelopeToPlay.Length) {
82 | step = EnvelopeToPlay.LoopIndex;
83 | }
84 | }
85 | }
86 |
87 | }
88 | else {
89 | // no release
90 | if (EnvelopeToPlay.HasLoop) {
91 | if (step >= EnvelopeToPlay.Length) {
92 | step = EnvelopeToPlay.LoopIndex;
93 | }
94 | }
95 | }
96 | if (step >= EnvelopeToPlay.Length) {
97 | EnvelopeEnded = true;
98 | step = EnvelopeToPlay.Length - 1;
99 | }
100 | else {
101 | EnvelopeEnded = false;
102 | }
103 | }
104 | Evaluate();
105 | }
106 |
107 | private void Evaluate() {
108 | if (EnvelopeToPlay == null) {
109 | Value = GetDefaultValue();
110 | }
111 | else if (!EnvelopeToPlay.IsActive) {
112 | Value = GetDefaultValue();
113 | }
114 | else if (step >= 0 && step < EnvelopeToPlay.values.Length) {
115 | try {
116 | Value = EnvelopeToPlay.values[step];
117 | } catch {
118 | Value = GetDefaultValue();
119 | }
120 | }
121 | else {
122 | Value = GetDefaultValue();
123 | }
124 | }
125 |
126 | public string GetState() {
127 | return Value + " " + step + " ";
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Source/Audio/StereoBiQuadFilter.cs:
--------------------------------------------------------------------------------
1 | using NAudio.Dsp;
2 |
3 | namespace WaveTracker.Audio {
4 | public class StereoBiQuadFilter {
5 | private BiQuadFilter filterL;
6 | private BiQuadFilter filterR;
7 |
8 | public StereoBiQuadFilter() {
9 | filterL = BiQuadFilter.LowPassFilter(AudioEngine.TrueSampleRate, AudioEngine.SampleRate / 2f, 1);
10 | filterR = BiQuadFilter.LowPassFilter(AudioEngine.TrueSampleRate, AudioEngine.SampleRate / 2f, 1);
11 | }
12 |
13 | public void SetLowpassFilter(float sampleRate, float cutoffFrequency, float q) {
14 | filterL.SetLowPassFilter(sampleRate, cutoffFrequency, q);
15 | filterR.SetLowPassFilter(sampleRate, cutoffFrequency, q);
16 | }
17 |
18 | public void Transform(float inputL, float inputR, out float outputL, out float outputR) {
19 | outputL = filterL.Transform(inputL);
20 | outputR = filterR.Transform(inputR);
21 | }
22 |
23 | public float TransformL(float input) {
24 | return filterL.Transform(input);
25 | }
26 | public float TransformR(float input) {
27 | return filterR.Transform(input);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Source/Audio/TickEvent.cs:
--------------------------------------------------------------------------------
1 | namespace WaveTracker.Audio {
2 | public class TickEvent {
3 | public TickEventType eventType;
4 | public int value;
5 | public int value2;
6 | public int countdown;
7 |
8 | public TickEvent(TickEventType eventType, int val, int val2, int timer) {
9 | this.eventType = eventType;
10 | value = val;
11 | value2 = val2;
12 | countdown = timer;
13 | }
14 |
15 | public void Update() {
16 | countdown--;
17 | }
18 | }
19 | public enum TickEventType {
20 | Note, Instrument, Volume, Effect
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/Input/KeyboardShortcut.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework.Input;
2 | using System;
3 | using System.Text.Json.Serialization;
4 |
5 | namespace WaveTracker {
6 | ///
7 | /// Structure containing a key and a key modifier
8 | ///
9 | [Serializable]
10 | public struct KeyboardShortcut {
11 | [JsonRequired]
12 | public Keys Key { get; set; }
13 | [JsonRequired]
14 | public KeyModifier Modifier { get; set; }
15 | public KeyboardShortcut(Keys key, KeyModifier modifier) {
16 | Key = key;
17 | Modifier = modifier;
18 | }
19 | public KeyboardShortcut(Keys key) {
20 | Key = key;
21 | Modifier = KeyModifier.None;
22 | }
23 | public KeyboardShortcut() {
24 | Key = Keys.None;
25 | Modifier = KeyModifier.None;
26 | }
27 | [JsonIgnore]
28 | public bool IsPressed {
29 | get { return Input.GetKey(Key, Modifier); }
30 | }
31 | [JsonIgnore]
32 | public bool IsPressedDown {
33 | get { return Input.GetKeyDown(Key, Modifier); }
34 | }
35 | [JsonIgnore]
36 | public bool IsPressedRepeat {
37 | get { return Input.GetKeyRepeat(Key, Modifier); }
38 | }
39 | [JsonIgnore]
40 | public bool WasReleasedThisFrame {
41 | get { return Input.GetKeyUp(Key, Modifier); }
42 | }
43 |
44 | public override string ToString() {
45 | return Key == Keys.None ? "(none)" : Helpers.ModifierToString(Modifier) + Helpers.KeyToString(Key);
46 | }
47 |
48 | public static bool operator ==(KeyboardShortcut one, KeyboardShortcut two) {
49 | return one.Key == two.Key && one.Modifier == two.Modifier;
50 | }
51 | public static bool operator !=(KeyboardShortcut one, KeyboardShortcut two) {
52 | return one.Key != two.Key || one.Modifier != two.Modifier;
53 | }
54 |
55 | public override bool Equals(object obj) {
56 | if (obj is KeyboardShortcut other) {
57 | if (other.Key == Key && other.Modifier == Modifier) {
58 | return true;
59 | }
60 | }
61 | return false;
62 | }
63 |
64 | public override int GetHashCode() {
65 | return (Key, Modifier).GetHashCode();
66 | }
67 |
68 | public static KeyboardShortcut None { get { return new KeyboardShortcut(Keys.None, KeyModifier.None); } }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/Source/Input/SystemClipboard.cs:
--------------------------------------------------------------------------------
1 | using TextCopy;
2 |
3 | namespace WaveTracker {
4 | ///
5 | /// Wrapper class for accessing multiple platform's clipboards
6 | ///
7 | public static class SystemClipboard {
8 | public static void SetText(string text) {
9 | ClipboardService.SetText(text);
10 | }
11 | public static string GetText() {
12 | return ClipboardService.GetText() ?? "";
13 | }
14 | public static bool HasText() {
15 | return ClipboardService.GetText() != null;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Source/Input/Tooltip.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 | using WaveTracker.Rendering;
3 | using WaveTracker.Tracker;
4 |
5 | namespace WaveTracker {
6 | public static class Tooltip {
7 | private static int hoverTime;
8 | private static int elapsedMS;
9 | private static int px, py;
10 | private static string lasttooltip;
11 | private static bool show;
12 | public static string TooltipText { get; set; }
13 | public static string TooltipTextLong { get; set; }
14 |
15 | public static char LastEffect { get; set; }
16 |
17 | public static void Update() {
18 | TooltipText = "";
19 | TooltipTextLong = "";
20 |
21 | elapsedMS = App.GameTime.ElapsedGameTime.Milliseconds;
22 |
23 | }
24 |
25 | ///
26 | /// Write bottom info bar
27 | ///
28 | private static void WriteInfo() {
29 | int y = App.WindowHeight - 8;
30 | // draw bar
31 | Graphics.DrawRect(0, y, App.WindowWidth, 9, new Color(230, 230, 230, 255));
32 | int x = App.WindowWidth - 40;
33 | Color textColor = UIColors.label;
34 | Graphics.WriteMonospaced(Playback.PlaybackTime.Minute.ToString("D2") + ":", x, y, textColor, 4);
35 | x += 12;
36 | Graphics.WriteMonospaced(Playback.PlaybackTime.Second.ToString("D2") + ":", x, y, textColor, 4);
37 | x += 12;
38 | Graphics.WriteMonospaced((Playback.PlaybackTime.Millisecond / 10).ToString("D2"), x, y, textColor, 4);
39 | x = App.WindowWidth - 100;
40 |
41 | float ticksPerBeat = (float)Playback.CurrentTicksPerRow * App.CurrentSong.RowHighlightSecondary;
42 | float ticksPerMinute = (float)App.CurrentModule.TickRate * 60;
43 | float BPM = ticksPerMinute / ticksPerBeat;
44 |
45 | Graphics.Write(BPM.ToString("0.00") + " BPM", x, y, textColor);
46 |
47 | x -= 46;
48 | Graphics.Write("Speed " + Playback.CurrentTicksPerRow, x, y, textColor);
49 | x -= 46;
50 | Graphics.Write(App.CurrentModule.TickRate + " Hz", x, y, textColor);
51 | if (TooltipTextLong != "" && TooltipTextLong != null) {
52 | Graphics.Write(TooltipTextLong, 2, y, new Color(58, 63, 94));
53 | LastEffect = '\0';
54 | }
55 | else if (LastEffect != '\0') {
56 | (string, string, string, string) effectDescription = Helpers.GetEffectDescription(LastEffect);
57 | if (effectDescription.Item1.Length > 0) {
58 | Graphics.Write(effectDescription.Item1 + " - " + effectDescription.Item2 + " " + effectDescription.Item3, 2, y, new Color(58, 63, 94));
59 | }
60 | }
61 |
62 | }
63 |
64 | public static void Draw() {
65 | WriteInfo();
66 | if (TooltipText != lasttooltip) {
67 | hoverTime = 0;
68 | lasttooltip = TooltipText;
69 | }
70 |
71 | if (TooltipText != "" && TooltipText != null) {
72 | hoverTime += elapsedMS;
73 | if (hoverTime > 500) {
74 | if (show == false) {
75 | px = Input.MousePositionX;
76 | py = Input.MousePositionY + 10;
77 | }
78 | show = true;
79 | int width = Helpers.GetWidthOfText(TooltipText) + 6;
80 | if (px + width + 1 > App.WindowWidth) {
81 | int diff = px + width + 1 - App.WindowWidth;
82 | px -= diff;
83 | }
84 | Graphics.DrawRect(px - 1, py - 1, width, 10, new Color(151, 156, 186));
85 |
86 | Graphics.DrawRect(px, py, width - 2, 8, new Color(255, 255, 255));
87 | Graphics.Write(TooltipText, px + 2, py, new Color(151, 156, 186));
88 | }
89 | else {
90 | show = false;
91 | }
92 | }
93 | else {
94 | show = false;
95 | hoverTime = 0;
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Source/Program.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 |
4 | namespace WaveTracker {
5 |
6 | ///
7 | /// The main class.
8 | ///
9 | public static class Program {
10 | ///
11 | /// The main entry point for the application.
12 | ///
13 | [STAThread]
14 | private static void Main(string[] args) {
15 | using (App game = new App(args)) {
16 | game.Run();
17 | }
18 |
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Source/Rendering/Graphics.cs:
--------------------------------------------------------------------------------
1 | using FontStashSharp;
2 | using Microsoft.Xna.Framework;
3 | using Microsoft.Xna.Framework.Content;
4 | using Microsoft.Xna.Framework.Graphics;
5 | using System.IO;
6 |
7 | namespace WaveTracker.Rendering {
8 | public static class Graphics {
9 | public static SpriteBatch spriteBatch;
10 | public static SpriteFont defaultFont;
11 |
12 | public static readonly RasterizerState scissorRasterizer = new RasterizerState() { CullMode = CullMode.None, ScissorTestEnable = true };
13 | public static FontSystem loadedFont;
14 | public static SpriteFontBase customFont;
15 |
16 | public static Texture2D pixel;
17 | public static Texture2D img;
18 | public static int Scale { get; set; }
19 | public static float fontScale = 2;
20 | public static float fontOffsetY = 2;
21 |
22 | public static readonly BlendState BlendState = new BlendState {
23 | ColorSourceBlend = Blend.SourceAlpha,
24 | AlphaSourceBlend = Blend.One,
25 | ColorDestinationBlend = Blend.InverseSourceAlpha,
26 | AlphaDestinationBlend = Blend.InverseSourceAlpha,
27 | };
28 |
29 | public static readonly SamplerState SamplerState = SamplerState.PointClamp;
30 | public static readonly DepthStencilState DepthStencilState = DepthStencilState.None;
31 | public static bool IsUsingCustomFont { get; private set; }
32 |
33 | public static void Initialize(ContentManager content, GraphicsDevice device) {
34 | img = content.Load("img");
35 | pixel = new Texture2D(device, 1, 1);
36 | pixel.SetData(new[] { Color.White });
37 | spriteBatch = new SpriteBatch(device);
38 | defaultFont = content.Load("wavetracker_font");
39 |
40 | IsUsingCustomFont = App.Settings.General.UseHighResolutionText;
41 | FontSystemSettings fontSettings = new FontSystemSettings();
42 | fontSettings.GlyphRenderer = (input, output, options) => {
43 | int size = options.Size.X * options.Size.Y;
44 | for (int i = 0; i < size; i++) {
45 | int ci = i * 4;
46 | output[ci] = output[ci + 1] = output[ci + 2] = 255; // set all color to white (smoother antialiasing on colored text)
47 | output[ci + 3] = input[i]; // keep alpha the same
48 | }
49 | };
50 | loadedFont = new FontSystem(fontSettings);
51 | try {
52 | string path = Path.Combine(content.RootDirectory, "high_resolution_font.ttf");
53 | loadedFont.AddFont(File.ReadAllBytes(path));
54 | } catch {
55 | IsUsingCustomFont = false;
56 | }
57 | SetFont();
58 | }
59 |
60 | public static void SetFont() {
61 | if (IsUsingCustomFont) {
62 | customFont = loadedFont.GetFont(9 * App.Settings.General.ScreenScale);
63 | fontScale = 1;
64 | fontOffsetY = 1;
65 | }
66 | else {
67 | fontScale = App.Settings.General.ScreenScale;
68 | fontOffsetY = 2;
69 | }
70 | }
71 |
72 | public static void DrawRect(int x, int y, int width, int height, Color color) {
73 | spriteBatch.Draw(pixel, new Rectangle(x * Scale, y * Scale, width * Scale, height * Scale), color);
74 | }
75 |
76 | public static void Write(string text, int x, int y, Color c) {
77 | if (IsUsingCustomFont) {
78 | customFont.DrawText(spriteBatch, text, new Vector2(x * Scale, (y - fontOffsetY) * Scale), c);
79 | }
80 | else {
81 | spriteBatch.DrawString(defaultFont, text, new Vector2(x * Scale, (y - fontOffsetY) * Scale), c, 0, Vector2.Zero, fontScale, SpriteEffects.None, 0);
82 | }
83 | }
84 |
85 | ///
86 | /// Renders multicolored text with colors for each character
87 | ///
88 | ///
89 | ///
90 | ///
91 | ///
92 | public static void Write(string text, int x, int y, Color[] colors) {
93 |
94 | if (IsUsingCustomFont) {
95 | float xpos = x;
96 | for (int i = 0; i < colors.Length; i++) {
97 | customFont.DrawText(spriteBatch, text[i] + "", new Vector2(xpos * Scale, (y - fontOffsetY) * Scale), colors[i]);
98 | xpos += customFont.MeasureString(text[i] + "").X / Scale;
99 | }
100 | }
101 | else {
102 | for (int i = 0; i < colors.Length; i++) {
103 | spriteBatch.DrawString(defaultFont, text[i] + "", new Vector2(x * Scale, (y - fontOffsetY) * Scale), colors[i], 0, Vector2.Zero, fontScale, SpriteEffects.None, 0);
104 | x += (int)defaultFont.MeasureString(text[i] + "").X;
105 | }
106 | }
107 | }
108 |
109 | public static void WriteRightJustified(string text, int x, int y, Color c) {
110 | Write(text, x - Helpers.GetWidthOfText(text), y, c);
111 | }
112 |
113 | public static void WriteMonospaced(string text, int x, int y, Color c, int width = 5) {
114 | foreach (char ch in text) {
115 | Write(ch + "", x, y, c);
116 | x += width + 1;
117 | }
118 | }
119 |
120 | public static void DrawSprite(int x, int y, int width, int height, Rectangle bounds, Color col) {
121 | spriteBatch.Draw(img, new Rectangle(x * Scale, y * Scale, width * Scale, height * Scale), bounds, col);
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Source/Rendering/HSLColor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 |
3 | namespace WaveTracker.Rendering {
4 | public struct HSLColor {
5 | ///
6 | /// Hue from 0.0-360.0
7 | ///
8 | public float H;
9 | ///
10 | /// Saturation from 0.0-1.0
11 | ///
12 | public float S;
13 | ///
14 | /// Lightness from 0.0-1.0
15 | ///
16 | public float L;
17 | ///
18 | /// Alpha from 0.0-1.0
19 | ///
20 | public float A;
21 |
22 | public HSLColor(float h, float s, float l, float a) {
23 | H = h;
24 | S = s;
25 | L = l;
26 | A = a;
27 | }
28 |
29 | public HSLColor(float h, float s, float l) {
30 | H = h;
31 | S = s;
32 | L = l;
33 | A = 1.0f;
34 | }
35 |
36 | public Color ToRGB() {
37 | byte r;
38 | byte g;
39 | byte b;
40 | if (S == 0) {
41 | r = g = b = (byte)(L * 255);
42 | }
43 | else {
44 | float v1, v2;
45 | float hue = H / 360;
46 |
47 | v2 = (L < 0.5) ? (L * (1 + S)) : (L + S - L * S);
48 | v1 = 2 * L - v2;
49 |
50 | r = (byte)(255 * HueToRGB(v1, v2, hue + 1.0f / 3));
51 | g = (byte)(255 * HueToRGB(v1, v2, hue));
52 | b = (byte)(255 * HueToRGB(v1, v2, hue - 1.0f / 3));
53 | }
54 |
55 | return new Color(r, g, b, (byte)(A * 255));
56 | }
57 | private static float HueToRGB(float v1, float v2, float vH) {
58 | if (vH < 0) {
59 | vH += 1;
60 | }
61 |
62 | if (vH > 1) {
63 | vH -= 1;
64 | }
65 |
66 | return 6 * vH < 1 ? v1 + (v2 - v1) * 6 * vH : 2 * vH < 1 ? v2 : 3 * vH < 2 ? v1 + (v2 - v1) * (2.0f / 3 - vH) * 6 : v1;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Source/Tracker/Instrument.cs:
--------------------------------------------------------------------------------
1 | using ProtoBuf;
2 | using System.Collections.Generic;
3 |
4 | namespace WaveTracker.Tracker {
5 | ///
6 | /// Base class for an instrument
7 | ///
8 | [ProtoContract]
9 | [ProtoInclude(10, typeof(WaveInstrument))]
10 | [ProtoInclude(20, typeof(SampleInstrument))]
11 | [ProtoInclude(30, typeof(NoiseInstrument))]
12 | public abstract class Instrument {
13 | [ProtoMember(1)]
14 | public string name;
15 | [ProtoMember(2)]
16 | public List envelopes;
17 |
18 | public Instrument() {
19 | name = "New Instrument";
20 | envelopes = [];
21 |
22 | }
23 |
24 | public bool HasEnvelope(Envelope.EnvelopeType type) {
25 | foreach (Envelope envelope in envelopes) {
26 | if (envelope.Type == type) {
27 | return true;
28 | }
29 | }
30 | return false;
31 | }
32 |
33 | public abstract Instrument Clone();
34 | public override string ToString() {
35 | return name;
36 | }
37 | public void SetName(string name) {
38 | this.name = name;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Source/Tracker/NoiseInstrument.cs:
--------------------------------------------------------------------------------
1 | namespace WaveTracker.Tracker {
2 | public class NoiseInstrument : Instrument {
3 | public NoiseInstrument() : base() {
4 | name = "New Noise Instrument";
5 | }
6 |
7 | public override NoiseInstrument Clone() {
8 | NoiseInstrument m = new NoiseInstrument();
9 | m.name = name;
10 | m.envelopes = [];
11 | foreach (Envelope envelope in envelopes) {
12 | m.envelopes.Add(envelope.Clone());
13 | }
14 | return m;
15 | }
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/Source/Tracker/PatternEditorPosition.cs:
--------------------------------------------------------------------------------
1 | namespace WaveTracker.Tracker {
2 | ///
3 | /// Stores a snapshot of cursor positions and selection information
4 | ///
5 | public struct PatternEditorPosition {
6 | public CursorPos CursorPosition { get; private set; }
7 | public CursorPos SelectionStart { get; private set; }
8 | public CursorPos SelectionEnd { get; private set; }
9 | public bool SelectionIsActive { get; private set; }
10 |
11 | public PatternEditorPosition(CursorPos cursorPosition, PatternSelection patternSelection) {
12 | CursorPosition = cursorPosition;
13 | SelectionStart = patternSelection.min;
14 | SelectionEnd = patternSelection.max;
15 | SelectionIsActive = patternSelection.IsActive;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Source/Tracker/PatternEditorState.cs:
--------------------------------------------------------------------------------
1 | namespace WaveTracker.Tracker {
2 |
3 | public record PatternEditorState {
4 | private string[] patternStrings;
5 | ///
6 | /// The position before this action was committed
7 | ///
8 | public PatternEditorPosition PrePosition { get; private set; }
9 | ///
10 | /// The position after this action was committed
11 | ///
12 | public PatternEditorPosition PostPosition { get; private set; }
13 |
14 | ///
15 | /// The song's frameSequence
16 | ///
17 | private byte[] frameSequence;
18 |
19 | public PatternEditorState(WTSong song, PatternEditorPosition previous, PatternEditorPosition next) {
20 | patternStrings = song.ForcePackPatternsToStrings();
21 | frameSequence = song.GetFrameSequenceAsByteArray();
22 | PrePosition = previous;
23 | PostPosition = next;
24 | }
25 |
26 | ///
27 | /// Writes this state's data into song
28 | ///
29 | ///
30 | public void RestoreIntoSong(WTSong song) {
31 | song.UnpackFrameSequence(frameSequence);
32 | song.UnpackPatternsFromStrings(patternStrings);
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/Source/Tracker/PatternSelection.cs:
--------------------------------------------------------------------------------
1 | namespace WaveTracker.Tracker {
2 | public class PatternSelection {
3 | public WTSong Song { get; private set; }
4 | ///
5 | /// The top-left position of the selection rectangle
6 | ///
7 | public CursorPos min;
8 | ///
9 | /// The bottom-right position of the selection rectangle
10 | ///
11 | public CursorPos max;
12 |
13 | public int Width { get; private set; }
14 | public int Height { get; private set; }
15 |
16 | public bool IsActive { get; set; }
17 |
18 | public void Set(WTSong song, CursorPos startPos, CursorPos endPos) {
19 | Song = song;
20 | min = startPos;
21 | max = endPos;
22 |
23 | // perform any swaps necessary
24 | if (startPos.IsBelow(endPos)) {
25 | min.Row = endPos.Row;
26 | min.Frame = endPos.Frame;
27 | max.Row = startPos.Row;
28 | max.Frame = startPos.Frame;
29 | }
30 | if (startPos.IsRightOf(endPos)) {
31 | max.Channel = startPos.Channel;
32 | max.Column = startPos.Column;
33 | min.Channel = endPos.Channel;
34 | min.Column = endPos.Column;
35 | }
36 |
37 | // if the user selected on the row column, set the selection to span all channels
38 | if (max.Channel == -1 && min.Channel == -1) {
39 | min.Channel = 0;
40 | min.Column = 0;
41 | max.Channel = App.CurrentModule.ChannelCount - 1;
42 | max.Column = song.GetLastCursorColumnOfChannel(max.Channel);
43 | }
44 |
45 | // adjust the cursor positions to fill all of mulit-digit fields like instrument, volume and effects
46 | if (max.Column == CursorColumnType.Instrument1) {
47 | max.Column = CursorColumnType.Instrument2;
48 | }
49 | else if (max.Column == CursorColumnType.Volume1) {
50 | max.Column = CursorColumnType.Volume2;
51 | }
52 | else if (max.Column is CursorColumnType.Effect1 or CursorColumnType.Effect1Param1) {
53 | max.Column = CursorColumnType.Effect1Param2;
54 | }
55 | else if (max.Column is CursorColumnType.Effect2 or CursorColumnType.Effect2Param1) {
56 | max.Column = CursorColumnType.Effect2Param2;
57 | }
58 | else if (max.Column is CursorColumnType.Effect3 or CursorColumnType.Effect3Param1) {
59 | max.Column = CursorColumnType.Effect3Param2;
60 | }
61 | else if (max.Column is CursorColumnType.Effect4 or CursorColumnType.Effect4Param1) {
62 | max.Column = CursorColumnType.Effect4Param2;
63 | }
64 |
65 | if (min.Column == CursorColumnType.Instrument2) {
66 | min.Column = CursorColumnType.Instrument1;
67 | }
68 | else if (min.Column == CursorColumnType.Volume2) {
69 | min.Column = CursorColumnType.Volume1;
70 | }
71 | else if (min.Column is CursorColumnType.Effect1Param1 or CursorColumnType.Effect1Param2) {
72 | min.Column = CursorColumnType.Effect1;
73 | }
74 | else if (min.Column is CursorColumnType.Effect2Param1 or CursorColumnType.Effect2Param2) {
75 | min.Column = CursorColumnType.Effect2;
76 | }
77 | else if (min.Column is CursorColumnType.Effect3Param1 or CursorColumnType.Effect3Param2) {
78 | min.Column = CursorColumnType.Effect3;
79 | }
80 | else if (min.Column is CursorColumnType.Effect4Param1 or CursorColumnType.Effect4Param2) {
81 | min.Column = CursorColumnType.Effect4;
82 | }
83 |
84 | if (min.Channel < 0) {
85 | min.Channel = 0;
86 | }
87 |
88 | if (max.Channel < 0) {
89 | max.Channel = 0;
90 | }
91 |
92 | if (max.Column > song.GetLastCursorColumnOfChannel(max.Channel)) {
93 | max.Column = song.GetLastCursorColumnOfChannel(max.Channel);
94 | }
95 |
96 | Width = max.CellColumn - min.CellColumn + 1;
97 | Height = max.Row - min.Row + 1;
98 | }
99 |
100 | public PatternSelection(WTSong song, CursorPos startPos, CursorPos endPos) {
101 | Set(song, startPos, endPos);
102 | }
103 |
104 | public PatternSelection() {
105 | IsActive = false;
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Source/Tracker/SampleInstrument.cs:
--------------------------------------------------------------------------------
1 | using ProtoBuf;
2 |
3 | namespace WaveTracker.Tracker {
4 |
5 | [ProtoContract]
6 | public class SampleInstrument : Instrument {
7 | [ProtoMember(21)]
8 | public Sample sample;
9 |
10 | public SampleInstrument() : base() {
11 | name = "New Sample Instrument";
12 | sample = new Sample();
13 | }
14 |
15 | public override SampleInstrument Clone() {
16 | SampleInstrument m = new SampleInstrument();
17 | m.name = name;
18 | m.envelopes = [];
19 | foreach (Envelope envelope in envelopes) {
20 | m.envelopes.Add(envelope.Clone());
21 | }
22 | m.sample.sampleDataL = new short[sample.sampleDataL.Length];
23 | m.sample.sampleDataR = new short[sample.sampleDataR.Length];
24 | for (int i = 0; i < sample.sampleDataL.Length; i++) {
25 | m.sample.sampleDataL[i] = sample.sampleDataL[i];
26 | if (sample.sampleDataR.Length != 0) {
27 | m.sample.sampleDataR[i] = sample.sampleDataR[i];
28 | }
29 | }
30 |
31 | m.sample.loopType = sample.loopType;
32 | m.sample.loopPoint = sample.loopPoint;
33 | m.sample.resampleMode = sample.resampleMode;
34 | m.sample.SetBaseKey(sample.BaseKey);
35 | m.sample.SetDetune(sample.Detune);
36 | m.sample.useInVisualization = sample.useInVisualization;
37 | return m;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Source/Tracker/WTFrame.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace WaveTracker.Tracker {
4 | ///
5 | /// Object in a song's frame sequence which determines what patterns to play.
6 | ///
7 | public class WTFrame {
8 |
9 | ///
10 | /// The index of the pattern this frame should reference
11 | ///
12 | private int patternIndex;
13 |
14 | ///
15 | /// The index of the pattern this frame should reference, automatically clamped between 0-99
16 | ///
17 | public int PatternIndex {
18 | get { return patternIndex; }
19 | set {
20 | if (patternIndex != value) {
21 | patternIndex = Math.Clamp(value, 0, 99);
22 | App.CurrentModule.SetDirty();
23 | }
24 | }
25 | }
26 |
27 | ///
28 | /// The song that owns this frame
29 | ///
30 | public WTSong ParentSong { get; private set; }
31 |
32 | ///
33 | /// Returns the pattern this frame references
34 | ///
35 | ///
36 | public WTPattern GetPattern() {
37 | return ParentSong.Patterns[PatternIndex];
38 | }
39 |
40 | ///
41 | /// Gets the length of this frame's pattern, taking into account Cxx, Bxx and Dxx commands
42 | ///
43 | ///
44 | public int GetLength() {
45 | return ParentSong.Patterns[PatternIndex].GetModifiedLength();
46 | }
47 |
48 | ///
49 | /// Creates a new frame that holds a pattern index and a reference to song that holds it.
50 | ///
51 | ///
52 | ///
53 | public WTFrame(int index, WTSong parent) {
54 | PatternIndex = index;
55 | ParentSong = parent;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Source/Tracker/WaveInstrument.cs:
--------------------------------------------------------------------------------
1 | namespace WaveTracker.Tracker {
2 | public class WaveInstrument : Instrument {
3 |
4 | public WaveInstrument() : base() {
5 | name = "New Wave Instrument";
6 | }
7 |
8 | public override WaveInstrument Clone() {
9 | WaveInstrument m = new WaveInstrument();
10 | m.name = name;
11 | m.envelopes = [];
12 | foreach (Envelope envelope in envelopes) {
13 | m.envelopes.Add(envelope.Clone());
14 | }
15 | return m;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Source/UI/Button.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 |
3 | namespace WaveTracker.UI {
4 | public class Button : Clickable {
5 | public string Label { get; private set; }
6 | public bool LabelIsCentered { get; set; }
7 |
8 | private int labelWidth;
9 |
10 | public Button(string label, int x, int y, Element parent) {
11 | enabled = true;
12 | this.x = x;
13 | this.y = y;
14 | Label = label;
15 | LabelIsCentered = true;
16 | width = Helpers.GetWidthOfText(label) + 8;
17 | labelWidth = Helpers.GetWidthOfText(label);
18 | height = 13;
19 | SetParent(parent);
20 | }
21 | public Button(string label, int x, int y, int width, Element parent) {
22 | enabled = true;
23 | this.x = x;
24 | this.y = y;
25 | Label = label;
26 | LabelIsCentered = true;
27 | this.width = width;
28 | labelWidth = Helpers.GetWidthOfText(label);
29 | height = 13;
30 | SetParent(parent);
31 | }
32 |
33 | public void SetLabel(string label) {
34 | Label = label;
35 | labelWidth = Helpers.GetWidthOfText(label);
36 | }
37 |
38 | private Color GetBackgroundColor() {
39 | if (IsPressed) {
40 | return ButtonColors.backgroundColorPressed;
41 | }
42 | else {
43 | if (IsHovered) {
44 | return ButtonColors.backgroundColorHover;
45 | }
46 | else {
47 | return ButtonColors.backgroundColor;
48 | }
49 | }
50 | }
51 |
52 | private Color GetTextColor() {
53 | if (IsPressed) {
54 | return ButtonColors.textColorPressed;
55 | }
56 | else {
57 | return ButtonColors.textColor;
58 | }
59 | }
60 |
61 | public void Draw() {
62 | if (enabled) {
63 | DrawRoundedRect(0, 0, width, height, GetBackgroundColor());
64 |
65 | int textOffset = IsPressed ? 1 : 0;
66 |
67 | if (LabelIsCentered) {
68 | Write(Label, (width - labelWidth) / 2, (height + 1) / 2 - 4 + textOffset, GetTextColor());
69 | }
70 | else {
71 | Write(Label, 4, (height + 1) / 2 - 4 + textOffset, GetTextColor());
72 | }
73 | }
74 | else {
75 | DrawRoundedRect(0, 0, width, height, ButtonColors.backgroundColorDisabled);
76 | if (LabelIsCentered) {
77 | Write(Label, (width - labelWidth) / 2, (height + 1) / 2 - 4, ButtonColors.textColorDisabled);
78 | }
79 | else {
80 | Write(Label, 4, (height + 1) / 2 - 4, ButtonColors.textColorDisabled);
81 | }
82 | }
83 |
84 | }
85 |
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Source/UI/ButtonColors.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 |
3 | namespace WaveTracker.UI {
4 | public class ButtonColors {
5 | public static readonly Color backgroundColor = new Color(104, 111, 153);
6 | public static readonly Color backgroundColorHover = new Color(132, 138, 172);
7 | public static readonly Color backgroundColorPressed = new Color(8, 124, 232);
8 | public static readonly Color textColor = new Color(255, 255, 255);
9 | public static readonly Color textColorPressed = new Color(255, 255, 255);
10 | public static readonly Color textColorDisabled = new Color(255, 255, 255);
11 | public static readonly Color backgroundColorDisabled = new Color(192, 195, 212);
12 | public static readonly Color toggleBackgroundColor = new Color(32, 144, 246);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Source/UI/Checkbox.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 |
3 | namespace WaveTracker.UI {
4 | public class Checkbox : Clickable {
5 | public bool Value { get; set; }
6 |
7 | public bool ValueWasChangedInternally;
8 |
9 | public Checkbox(int x, int y, Element parent) {
10 | this.x = x;
11 | this.y = y;
12 | width = 9;
13 | height = 9;
14 | SetParent(parent);
15 | }
16 |
17 | public void Update() {
18 | if (Clicked) {
19 | Value = !Value;
20 | ValueWasChangedInternally = true;
21 | }
22 | else {
23 | ValueWasChangedInternally = false;
24 | }
25 | }
26 |
27 | private Rectangle GetBounds(int num) {
28 | return new Rectangle(400, 144 + num * 9, 9, 9);
29 | }
30 |
31 | public void DrawAsTabToggle() {
32 | if (Value) {
33 | DrawSprite(0, 0, GetBounds(3));
34 | }
35 | else {
36 | DrawSprite(0, 0, GetBounds(6));
37 | }
38 | }
39 |
40 | public void Draw(bool isHovered, bool isPressed) {
41 | if (enabled) {
42 | if (Value) {
43 | if (isHovered) {
44 | if (isPressed) {
45 | DrawSprite(0, 0, GetBounds(5));
46 | }
47 | else {
48 | DrawSprite(0, 0, GetBounds(4));
49 | }
50 | }
51 | else {
52 | DrawSprite(0, 0, GetBounds(3));
53 | }
54 | }
55 | else {
56 | if (isHovered) {
57 | if (isPressed) {
58 | DrawSprite(0, 0, GetBounds(2));
59 | }
60 | else {
61 | DrawSprite(0, 0, GetBounds(1));
62 | }
63 | }
64 | else {
65 | DrawSprite(0, 0, GetBounds(0));
66 | }
67 | }
68 |
69 | }
70 | else {
71 | if (Value) {
72 | DrawSprite(0, 0, GetBounds(7));
73 | }
74 | else {
75 | DrawSprite(0, 0, GetBounds(6));
76 | }
77 | }
78 |
79 | }
80 |
81 | public void Draw() {
82 | if (enabled) {
83 | if (Value) {
84 | if (IsHovered) {
85 | if (IsPressed) {
86 | DrawSprite(0, 0, GetBounds(5));
87 | }
88 | else {
89 | DrawSprite(0, 0, GetBounds(4));
90 | }
91 | }
92 | else {
93 | DrawSprite(0, 0, GetBounds(3));
94 | }
95 | }
96 | else {
97 | if (IsHovered) {
98 | if (IsPressed) {
99 | DrawSprite(0, 0, GetBounds(2));
100 | }
101 | else {
102 | DrawSprite(0, 0, GetBounds(1));
103 | }
104 | }
105 | else {
106 | DrawSprite(0, 0, GetBounds(0));
107 | }
108 | }
109 |
110 | }
111 | else {
112 | if (Value) {
113 | DrawSprite(0, 0, GetBounds(7));
114 | }
115 | else {
116 | DrawSprite(0, 0, GetBounds(6));
117 | }
118 | }
119 |
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Source/UI/CheckboxLabeled.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 |
3 | namespace WaveTracker.UI {
4 | public class CheckboxLabeled : Clickable {
5 | public bool Value { get; set; }
6 |
7 | private string label;
8 | public bool ShowCheckboxOnRight { get; set; }
9 |
10 | public CheckboxLabeled(string label, int x, int y, int width, Element parent) {
11 | this.x = x;
12 | this.y = y;
13 | this.width = width;
14 | this.label = label;
15 | height = 11;
16 | SetParent(parent);
17 | }
18 |
19 | public CheckboxLabeled(string label, int x, int y, Element parent) {
20 | this.x = x;
21 | this.y = y;
22 | width = 16 + Helpers.GetWidthOfText(label);
23 | this.label = label;
24 | height = 11;
25 | SetParent(parent);
26 | }
27 |
28 | public void Update() {
29 | if (Clicked) {
30 | Value = !Value;
31 | }
32 | }
33 |
34 | private Rectangle GetBounds(int num) {
35 | return new Rectangle(400, 144 + num * 9, 9, 9);
36 | }
37 |
38 | public void Draw() {
39 | int checkX = ShowCheckboxOnRight ? width - 9 : 0;
40 | if (enabled) {
41 | if (Value) {
42 | if (IsHovered) {
43 | if (IsPressed) {
44 | DrawSprite(checkX, (height - 9) / 2, GetBounds(5));
45 | }
46 | else {
47 | DrawSprite(checkX, (height - 9) / 2, GetBounds(4));
48 | }
49 | }
50 | else {
51 | DrawSprite(checkX, (height - 9) / 2, GetBounds(3));
52 | }
53 | }
54 | else {
55 | if (IsHovered) {
56 | if (IsPressed) {
57 | DrawSprite(checkX, (height - 9) / 2, GetBounds(2));
58 | }
59 | else {
60 | DrawSprite(checkX, (height - 9) / 2, GetBounds(1));
61 | }
62 | }
63 | else {
64 | DrawSprite(checkX, (height - 9) / 2, GetBounds(0));
65 | }
66 | }
67 |
68 | }
69 | else {
70 | if (Value) {
71 | DrawSprite(checkX, (height - 9) / 2, GetBounds(7));
72 | }
73 | else {
74 | DrawSprite(checkX, (height - 9) / 2, GetBounds(6));
75 | }
76 | }
77 | Color labelCol = UIColors.labelDark;
78 | if (IsHovered) {
79 | labelCol = Color.Black;
80 | }
81 |
82 | Write(label + "", ShowCheckboxOnRight ? 0 : 13, (height - 7) / 2, labelCol);
83 | }
84 |
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Source/UI/Clickable.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 |
3 | namespace WaveTracker.UI {
4 | public abstract class Clickable : Element {
5 | public int width;
6 | public int height;
7 |
8 | public int BoundsBottom {
9 | get {
10 | return y + height;
11 | }
12 | }
13 |
14 | public int BoundsRight {
15 | get {
16 | return x + width;
17 | }
18 | }
19 |
20 | public bool enabled = true;
21 | public string TooltipText {
22 | get; private set;
23 | }
24 |
25 | public string TooltipTextLong { get; private set; }
26 |
27 | public void SetTooltip(string ttshort, string ttlong) {
28 | TooltipText = ttshort;
29 | TooltipTextLong = ttlong;
30 | }
31 | public void SetTooltip(string ttshort) {
32 | TooltipText = ttshort;
33 | TooltipTextLong = ttshort;
34 | }
35 |
36 | public bool IsHovered {
37 | get {
38 | if (!InFocus) {
39 | return false;
40 | }
41 |
42 | if (MouseX < width && MouseY < height && MouseX >= 0 && MouseY >= 0) {
43 | if (TooltipTextLong != "") {
44 | Tooltip.TooltipTextLong = TooltipTextLong;
45 | }
46 |
47 | if (TooltipText != "") {
48 | Tooltip.TooltipText = TooltipText;
49 | }
50 |
51 | return true;
52 | }
53 | return false;
54 | }
55 | }
56 |
57 | ///
58 | /// Returns true if this element is hovered, regardless of if it is in focus or not
59 | ///
60 | public bool IsMouseOverRegion {
61 | get {
62 | return MouseX < width && MouseY < height && MouseX >= 0 && MouseY >= 0;
63 | }
64 | }
65 | public bool IsPressed { get { return IsHovered && Input.GetClick(KeyModifier._Any) && GlobalPointIsInBounds(Input.LastClickLocation) && IsMeOrAParent(Input.lastClickFocus); } }
66 |
67 | public bool Clicked {
68 | get {
69 | return enabled && IsHovered && Input.GetClickUp(KeyModifier._Any) && GlobalPointIsInBounds(Input.LastClickLocation) && GlobalPointIsInBounds(Input.LastClickReleaseLocation) && IsMeOrAParent(Input.lastClickFocus);
70 | }
71 | }
72 |
73 | public bool RightClicked {
74 | get {
75 | return enabled && IsHovered && Input.GetRightClickUp(KeyModifier._Any) && GlobalPointIsInBounds(Input.LastRightClickLocation) && GlobalPointIsInBounds(Input.LastRightClickReleaseLocation);
76 | }
77 | }
78 |
79 | public bool RightClickedDown {
80 | get {
81 | return enabled && IsHovered && Input.GetRightClickDown(KeyModifier._Any) && GlobalPointIsInBounds(Input.LastRightClickLocation);
82 | }
83 | }
84 |
85 | public bool ClickedDown {
86 | get {
87 | return enabled && IsHovered && Input.GetClickDown(KeyModifier._Any) && GlobalPointIsInBounds(Input.LastClickLocation) && IsMeOrAParent(Input.lastClickFocus);
88 | }
89 | }
90 |
91 | public bool DoubleClicked {
92 | get {
93 | return enabled && IsHovered && Input.GetDoubleClickDown(KeyModifier._Any) && GlobalPointIsInBounds(Input.LastClickLocation) && GlobalPointIsInBounds(Input.LastClickReleaseLocation) && IsMeOrAParent(Input.lastClickFocus);
94 | }
95 | }
96 |
97 | public bool ClickedM(KeyModifier modifier) {
98 | return InFocus
99 | && enabled && IsHovered && Input.GetClickUp(modifier) && GlobalPointIsInBounds(Input.LastClickLocation) && GlobalPointIsInBounds(Input.LastClickReleaseLocation) && IsMeOrAParent(Input.lastClickFocus);
100 | }
101 |
102 | public bool SingleClickedM(KeyModifier modifier) {
103 | return InFocus
104 | && enabled && IsHovered && Input.GetSingleClickUp(modifier) && GlobalPointIsInBounds(Input.LastClickLocation) && GlobalPointIsInBounds(Input.LastClickReleaseLocation) && IsMeOrAParent(Input.lastClickFocus);
105 | ;
106 | }
107 |
108 | public bool IsPressedM(KeyModifier modifier) {
109 | return IsHovered && Input.GetClick(modifier) && GlobalPointIsInBounds(Input.LastClickLocation) && IsMeOrAParent(Input.lastClickFocus);
110 | }
111 |
112 | public bool DoubleClickedM(KeyModifier modifier) {
113 | return enabled && IsHovered && Input.GetDoubleClickDown(modifier) && IsMeOrAParent(Input.lastClickFocus);
114 | }
115 |
116 | public bool GlobalPointIsInBounds(Point p) {
117 | return p.X >= GlobalX && p.Y >= GlobalY && p.X < GlobalX + width && p.Y < GlobalY + height;
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Source/UI/ColorButton.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 |
3 | namespace WaveTracker.UI {
4 | public class ColorButton : Clickable {
5 | public Color Color { get; set; }
6 | public bool NeverShowAlpha { get; set; }
7 | public bool DrawBorder { get; set; }
8 | public string HexValue {
9 | get { return Color.GetHexCode(); }
10 | set { Color = Helpers.HexCodeToColor(value); }
11 | }
12 |
13 | public ColorButton(Color color, int x, int y, Element parent) {
14 | enabled = true;
15 | this.x = x;
16 | this.y = y;
17 | Color = color;
18 | width = 62;
19 | height = 13;
20 | DrawBorder = true;
21 | NeverShowAlpha = false;
22 | SetParent(parent);
23 | }
24 |
25 | public void Update() {
26 | if (Clicked) {
27 | Dialogs.colorPicker.Open(this);
28 | }
29 | }
30 |
31 | public void Draw() {
32 | Color displayColor = Color;
33 | if (IsPressed) {
34 | displayColor = displayColor.ToNegative();
35 | }
36 |
37 | Color textColor = (displayColor.R * 30 + displayColor.G * 59 + displayColor.B * 11) / 100 < 128 ? Color.White : Color.Black;
38 | if (DrawBorder) {
39 | DrawRect(0, 0, width, height, ButtonColors.backgroundColor);
40 | }
41 | DrawSprite(1, 1, width - 2, height - 2, new Rectangle(416, 208, 4, 2), Color.White);
42 |
43 | // draw color
44 | DrawRect(1, 1, width - 2, height - 2, displayColor);
45 |
46 | Color outlineColor = IsHovered ? textColor : Helpers.Alpha(Color.White, 80);
47 | DrawRect(1, 1, width - 2, 1, outlineColor);
48 | DrawRect(1, 2, 1, height - 4, outlineColor);
49 | DrawRect(1, height - 2, width - 2, 1, outlineColor);
50 | DrawRect(width - 2, 2, 1, height - 4, outlineColor);
51 |
52 | string label = "#" + (NeverShowAlpha ? Color.GetHexCodeIgnoringAlpha() : Color.GetHexCode());
53 | int labelWidth = Helpers.GetWidthOfText(label);
54 | Write(label, (width - labelWidth) / 2, (height + 1) / 2 - 4, textColor);
55 | }
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/Source/UI/ColorButtonList.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace WaveTracker.UI {
6 | public class ColorButtonList : Clickable {
7 | private Dictionary entries;
8 | private Scrollbar scrollbar;
9 | private int numRows;
10 | private const int ROW_HEIGHT = 17;
11 |
12 | public ColorButtonList(int x, int y, int width, int numVisibleRows, Element parent) {
13 | this.x = x;
14 | this.y = y;
15 | this.width = width;
16 | height = numVisibleRows * ROW_HEIGHT;
17 | numRows = numVisibleRows;
18 | scrollbar = new Scrollbar(0, 0, width, height, this);
19 |
20 | SetParent(parent);
21 | }
22 |
23 | ///
24 | /// Sets the list for this box to reference
25 | ///
26 | public void SetDictionary(Dictionary colors) {
27 | entries = [];
28 | foreach (KeyValuePair pair in colors) {
29 | ColorButton button = new ColorButton(pair.Value, 0, 0, this);
30 | button.DrawBorder = false;
31 | entries.Add(pair.Key, button);
32 | }
33 | }
34 |
35 | public void ResetView() {
36 | scrollbar.ScrollValue = 0;
37 | }
38 |
39 | ///
40 | /// Saves the list in this box to the given dictionary
41 | ///
42 | public void SaveDictionaryInto(Dictionary colors) {
43 | foreach (KeyValuePair pair in entries) {
44 | colors[pair.Key] = pair.Value.Color;
45 | }
46 | }
47 |
48 | public void Update() {
49 | if (InFocus) {
50 | scrollbar.SetSize(entries.Count, numRows);
51 | scrollbar.UpdateScrollValue();
52 | scrollbar.Update();
53 | int rowNum = numRows - 1;
54 | for (int i = numRows + scrollbar.ScrollValue - 1; i >= scrollbar.ScrollValue; i--) {
55 | entries.ElementAt(i).Value.x = 2;
56 | entries.ElementAt(i).Value.y = 2 + rowNum * ROW_HEIGHT;
57 | entries.ElementAt(i).Value.Update();
58 | rowNum--;
59 | }
60 | }
61 | }
62 |
63 | public void Draw() {
64 | scrollbar.Draw();
65 |
66 | Color bgColor = new Color(43, 49, 81);
67 | Color bgColor2 = new Color(59, 68, 107);
68 |
69 | int rowNum = numRows - 1;
70 | for (int i = numRows + scrollbar.ScrollValue - 1; i >= scrollbar.ScrollValue; i--) {
71 | DrawRect(0, rowNum * ROW_HEIGHT, width - 6, ROW_HEIGHT, i % 2 == 0 ? bgColor2 : bgColor);
72 | entries.ElementAt(i).Value.Draw();
73 | Write(entries.ElementAt(i).Key, 68, rowNum * ROW_HEIGHT + 5, Color.White);
74 | rowNum--;
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Source/UI/ContextMenu.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework.Input;
2 |
3 | namespace WaveTracker.UI {
4 | public static class ContextMenu {
5 | private static Menu contextMenu;
6 |
7 | public static void Open(Menu menu) {
8 | if (contextMenu != menu) {
9 | contextMenu?.Close();
10 | contextMenu = menu;
11 | }
12 | contextMenu.SetPositionClamped(Input.MousePositionX, Input.MousePositionY);
13 | contextMenu.Open();
14 | }
15 |
16 | public static void CloseCurrent() {
17 | contextMenu?.Close();
18 | }
19 |
20 | public static void Update() {
21 | if (contextMenu != null) {
22 | if (Input.GetKeyDown(Keys.Escape, KeyModifier.None)) {
23 | contextMenu.Close();
24 | return;
25 | }
26 | contextMenu.Update();
27 | if (contextMenu != null) {
28 | if (contextMenu.enabled == false) {
29 | contextMenu = null;
30 | }
31 | }
32 | }
33 | }
34 |
35 | public static void Draw() {
36 | contextMenu?.Draw();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Source/UI/Dialog.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace WaveTracker.UI {
4 | ///
5 | /// A window with buttons along the bottom
6 | ///
7 | public abstract class Dialog : Window {
8 | private List