1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
#ifdef BUILD_UI
#include "SequenceDriver.h"
#include <algorithm>
#include <unordered_map>
#include "ui/BaseBackend.h"
#include "ui/ExportUtils.h"
#include "ui/Widgets.h"
namespace UI {
namespace {
RenderedAudio sRendered;
std::string sRenderedName;
int sRenderedOption = -1;
std::string sPlayingName;
std::unordered_map<std::string, int> sOptionChoice;
bool RenderCached(SequenceDriver* driver, const ParseResultData& item, int option) {
if (sRenderedName == item.name && sRenderedOption == option) {
return !sRendered.pcm.empty();
}
sRendered = {};
sRenderedName = item.name;
sRenderedOption = option;
if (!driver->Render(item, option, sRendered)) {
sRendered = {};
return false;
}
sRendered.seconds = (double)sRendered.pcm.size() / 2.0 / kSynthRate;
return !sRendered.pcm.empty();
}
} // namespace
float SequencePreviewUI::GetItemHeight(const ParseResultData&) {
return ImGui::GetTextLineHeightWithSpacing() * 3.0f + ImGui::GetFrameHeightWithSpacing() * 2.0f +
ImGui::GetStyle().ItemSpacing.y * 4.0f;
}
void SequencePreviewUI::DrawUI(const ParseResultData& item) {
UI::AssetHeader(item.name, item.type);
SequenceDriver* driver = GetSequenceDriver(item.type);
if (driver == nullptr || !item.data.has_value()) {
ImGui::TextDisabled(driver == nullptr ? "no sequence driver for this type" : "no data");
return;
}
ImGui::TextDisabled("sequence");
const std::vector<std::string> options = driver->Options(item);
auto [choiceIt, inserted] = sOptionChoice.try_emplace(item.name, 0);
if (inserted) {
choiceIt->second = driver->DefaultOption(item);
}
int& choice = choiceIt->second;
if (!options.empty()) {
choice = std::clamp(choice, 0, (int)options.size() - 1);
ImGui::SetNextItemWidth(std::min(ImGui::GetContentRegionAvail().x * 0.4f, 260.0f));
if (ImGui::BeginCombo("##seqopt", options[choice].c_str())) {
for (int i = 0; i < (int)options.size(); ++i) {
if (ImGui::Selectable(options[i].c_str(), i == choice)) {
choice = i;
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
}
const bool playingThis = sPlayingName == item.name && GetBackend()->AudioProgress() >= 0.0f;
if (ImGui::Button(playingThis ? "Stop##seq" : "Play##seq")) {
if (playingThis) {
GetBackend()->StopAudio();
sPlayingName.clear();
} else if (RenderCached(driver, item, choice)) {
GetBackend()->SetAudioSpeed(1.0f);
if (GetBackend()->PlaySamples(sRendered.pcm.data(), sRendered.pcm.size() / 2, kSynthRate, 2)) {
sPlayingName = item.name;
}
}
}
ImGui::SameLine();
if (ImGui::Button("WAV##seqexp")) {
if (RenderCached(driver, item, choice)) {
const auto path = ExportFilePath(item.name, "wav");
NoteExport(item.name, WriteWavFile(path, sRendered.pcm.data(), sRendered.pcm.size() / 2, 2, kSynthRate)
? path.string()
: "export failed");
} else {
NoteExport(item.name, "render failed");
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Export rendered sequence to torch-exports/");
}
DrawExportMarker(item.name);
ImGui::SameLine();
float volume = GetBackend()->GetAudioVolume();
ImGui::SetNextItemWidth(140.0f);
if (ImGui::SliderFloat("##seqvol", &volume, 0.0f, 1.0f, "vol %.2f")) {
GetBackend()->SetAudioVolume(volume);
}
ImGui::SameLine();
if (sRenderedName == item.name && !sRendered.pcm.empty()) {
ImGui::TextDisabled("%zu notes, %.1fs", sRendered.noteCount, sRendered.seconds);
} else {
ImGui::TextDisabled("press play to render");
}
float progress = playingThis ? std::max(GetBackend()->AudioProgress(), 0.0f) : 0.0f;
ImGui::SetNextItemWidth(std::min(ImGui::GetContentRegionAvail().x, 420.0f));
if (ImGui::SliderFloat("##seqseek", &progress, 0.0f, 1.0f, "") && playingThis) {
GetBackend()->SeekAudio(progress);
}
}
} // namespace UI
#endif // BUILD_UI
|