Loading...
Searching...
No Matches
plugins/score-plugin-avnd/Crousti/File.hpp
1#pragma once
2#include <Media/AudioDecoder.hpp>
3
4#include <score/document/DocumentContext.hpp>
5#include <score/tools/File.hpp>
6
7#include <ossia/dataflow/exec_state_facade.hpp>
8#include <ossia/dataflow/nodes/media.hpp>
9#include <ossia/network/value/value.hpp>
10
11#include <QByteArray>
12#include <QDebug>
13#include <QFile>
14
15#include <avnd/binding/ossia/soundfiles.hpp>
16#include <libremidi/reader.hpp>
17
18namespace oscr
19{
20
21namespace
22{
23[[nodiscard]] static QString
24filenameFromPort(const ossia::value& value, const score::DocumentContext& ctx)
25{
26 if(auto str = value.target<std::string>())
27 return score::locateFilePath(QString::fromStdString(*str).trimmed(), ctx);
28 return {};
29}
30
31// Resolve <PROJECT>: / <LIBRARY>: / document-relative paths for a path-valued
32// string control (e.g. a folder_port) exactly like the file ports above do, so
33// the object sees an absolute path instead of the raw library-relative string.
34// A non-string or empty value is returned unchanged (an unset control stays
35// empty rather than resolving to the document folder).
36[[nodiscard]] static ossia::value
37resolvePathValue(const ossia::value& value, const score::DocumentContext& ctx)
38{
39 if(auto str = value.target<std::string>(); str && !str->empty())
40 {
41 const QString resolved
42 = score::locateFilePath(QString::fromStdString(*str).trimmed(), ctx);
43 if(!resolved.isEmpty())
44 return ossia::value{resolved.toStdString()};
45 }
46 return value;
47}
48
49// TODO refactor this into a generic explicit soundfile loaded mechanism
50[[nodiscard]] static auto
51loadSoundfile(const ossia::value& value, const score::DocumentContext& ctx, double rate)
52{
53 // Initialize the control with the current soundfile
54 if(auto str = filenameFromPort(value, ctx); !str.isEmpty())
55 {
56 auto dec = Media::AudioDecoder::decode_synchronous(str, rate);
57
58 if(dec.has_value())
59 {
60 auto hdl = std::make_shared<ossia::audio_data>();
61 hdl->data = std::move(dec->second);
62 hdl->path = str.toStdString();
63 hdl->rate = rate;
64 return hdl;
65 }
66 }
67 return ossia::audio_handle{};
68}
69
70using midifile_handle = std::shared_ptr<oscr::midifile_data>;
71[[nodiscard]] inline midifile_handle
72loadMidifile(const ossia::value& value, const score::DocumentContext& ctx)
73{
74 // Initialize the control with the current soundfile
75 if(auto str = filenameFromPort(value, ctx); !str.isEmpty())
76 {
77 QFile f(str);
78 if(!f.open(QIODevice::ReadOnly))
79 return {};
80 auto ptr = f.map(0, f.size());
81
82 auto hdl = std::make_shared<oscr::midifile_data>();
83 if(auto ret = hdl->reader.parse((uint8_t*)ptr, f.size());
84 ret == libremidi::reader::invalid)
85 return {};
86
87 hdl->filename = str.toStdString();
88 return hdl;
89 }
90 return {};
91}
92
93using raw_file_handle = std::shared_ptr<raw_file_data>;
94[[nodiscard]] inline raw_file_handle loadRawfile(
95 const ossia::value& value, const score::DocumentContext& ctx, bool text, bool mmap)
96{
97 // A file port whose control holds no path at all is unset: that is
98 // not a failure and must stay silent. Everything below IS a failure, and
99 // every caller of this function treats a null handle as "do nothing" --
100 // ExecutorPortSetup.hpp and GpuUtils.hpp all wrap the call in
101 // `if(auto hdl = loadRawfile(...))`, so the object's own preprocessing
102 // (Field::process) is never even reached. Without a word here, pointing a
103 // file port at a path that does not exist produces NOTHING anywhere: no
104 // load, no callback, no message. That is the normal case, not an edge one --
105 // a document written on another machine carries absolute paths that do not
106 // resolve here.
107 const auto* raw = value.target<std::string>();
108 if(!raw || raw->empty())
109 return {};
110
111 const QString filename = filenameFromPort(value, ctx);
112 if(filename.isEmpty())
113 {
114 qWarning() << "file port: could not resolve"
115 << QString::fromStdString(*raw);
116 return {};
117 }
118
119 {
120 if(!QFile::exists(filename))
121 {
122 qWarning() << "file port: no such file:" << filename;
123 return {};
124 }
125
126 auto hdl = std::make_shared<oscr::raw_file_data>();
127 hdl->file.setFileName(filename);
128 if(!hdl->file.open(QIODevice::ReadOnly))
129 {
130 qWarning() << "file port: cannot open" << filename << ":"
131 << hdl->file.errorString();
132 return {};
133 }
134
135 if(mmap)
136 {
137 auto map = (char*)hdl->file.map(0, hdl->file.size());
138 hdl->data = QByteArray::fromRawData(map, hdl->file.size());
139 }
140 else
141 {
142 if(text)
143 hdl->file.setTextModeEnabled(true);
144
145 hdl->data = hdl->file.readAll();
146 }
147 hdl->filename = filename.toStdString();
148 return hdl;
149 }
150 return {};
151}
152
153[[nodiscard]] inline auto loadSoundfile(
154 const ossia::value& value, const score::DocumentContext& ctx,
155 const std::shared_ptr<ossia::execution_state>& st)
156{
157 const double rate = ossia::exec_state_facade{st.get()}.sampleRate();
158 return loadSoundfile(value, ctx, rate);
159}
160
161template <typename Field>
162static auto executePortPreprocess(auto& file)
163{
164 using field_file_type = decltype(Field::file);
165 field_file_type ffile;
166 ffile.bytes = decltype(ffile.bytes)(file.data.constData(), file.file.size());
167 ffile.filename = file.filename;
168 return Field::process(ffile);
169}
170
171}
172
173}
Definition Controls.hpp:27
QString locateFilePath(const QString &filename) noexcept
Definition File.cpp:84
Definition DocumentContext.hpp:18