melhorando build
This commit is contained in:
parent
3c6489f2a0
commit
596d0dcfb8
|
|
@ -1,580 +1,172 @@
|
||||||
---
|
---
|
||||||
layout: default
|
layout: default
|
||||||
title: "{{ page.file }}"
|
title: "Visualizando Projeto"
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="publication">
|
<div id="project-loader" class="section has-text-centered">
|
||||||
|
<button class="button is-loading is-large is-ghost">
|
||||||
|
Carregando Projeto...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="project-content" class="publication is-hidden">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<div class="tabs is-centered is-boxed is-medium mb-6">
|
<div class="tabs is-centered is-boxed is-medium mb-6">
|
||||||
{% include sidebar.html %}
|
{% include sidebar.html %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
id="project-header"
|
||||||
class="box has-background-white-ter mb-5"
|
class="box has-background-white-ter mb-5"
|
||||||
style="
|
style="
|
||||||
border-top: 4px solid #3273dc;
|
border-top: 4px solid #3273dc;
|
||||||
padding: 8px 16px !important;
|
padding: 8px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
min-height: unset;
|
|
||||||
"
|
"
|
||||||
>
|
></div>
|
||||||
<div style="display: flex; align-items: center; gap: 15px">
|
|
||||||
<h1
|
|
||||||
class="title is-5 m-0 p-0"
|
|
||||||
style="white-space: nowrap; line-height: 1; margin: 0 !important"
|
|
||||||
>
|
|
||||||
<code>{{ page.file }}</code>
|
|
||||||
</h1>
|
|
||||||
{% if page.bpm %}
|
|
||||||
<span
|
|
||||||
class="tag is-dark is-small"
|
|
||||||
style="height: 20px; line-height: 20px"
|
|
||||||
>🎵 {{ page.bpm }}</span
|
|
||||||
>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if page.file %}
|
|
||||||
<div style="display: flex; align-items: center; gap: 15px">
|
|
||||||
{% assign creation_url = '/mmpSearch/creation.html?project=' | append:
|
|
||||||
page.file %} {% assign embed_url = creation_url | append: '&embed=true'
|
|
||||||
%}
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="button is-small is-info is-light js-open-modal"
|
|
||||||
data-project-title="{{ page.file }}"
|
|
||||||
data-embed-url="{{ embed_url }}"
|
|
||||||
style="height: 25px; border-radius: 4px; font-weight: 600"
|
|
||||||
>
|
|
||||||
<span class="icon is-small"><i class="fa-solid fa-eye"></i></span>
|
|
||||||
<span class="is-hidden-mobile">Espiar Editor</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div style="width: 1px; height: 20px; background-color: #ddd"></div>
|
|
||||||
|
|
||||||
<div style="display: flex; align-items: center">
|
|
||||||
<span
|
|
||||||
class="is-size-7 mr-2 has-text-grey is-hidden-mobile"
|
|
||||||
style="margin-bottom: 0 !important"
|
|
||||||
>Preview:</span
|
|
||||||
>
|
|
||||||
<div class="control">
|
|
||||||
{% assign audio_file_path = 'src_mmpSearch/wav/' | append: page.file |
|
|
||||||
append: '.ogg' %}
|
|
||||||
<audio
|
|
||||||
controls
|
|
||||||
style="
|
|
||||||
height: 25px;
|
|
||||||
max-width: 220px;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin: 0;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<source
|
|
||||||
src="{{ audio_file_path | relative_url }}"
|
|
||||||
type="audio/wav"
|
|
||||||
/>
|
|
||||||
{% assign audio_ogg_path = 'src_mmpSearch/wav/' | append: page.file |
|
|
||||||
append: '.ogg' %}
|
|
||||||
<source
|
|
||||||
src="{{ audio_ogg_path | relative_url }}"
|
|
||||||
type="audio/ogg"
|
|
||||||
/>
|
|
||||||
</audio>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-8">
|
<div class="column is-8" id="main-content"></div>
|
||||||
{% if page.tags %} {% assign tags_vazias = true %} {% for categoria in
|
<div class="column is-4" id="sidebar-content"></div>
|
||||||
page.tags %} {% if categoria[1] and categoria[1].size > 0 %}{% assign
|
|
||||||
tags_vazias = false %}{% endif %} {% endfor %} {% unless tags_vazias %}
|
|
||||||
<details
|
|
||||||
class="card mb-4"
|
|
||||||
open
|
|
||||||
style="
|
|
||||||
background-color: #f0f8ff;
|
|
||||||
border: 1px solid #cfe8fc;
|
|
||||||
border-radius: 8px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<summary
|
|
||||||
class="card-header"
|
|
||||||
style="
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #e1f0fa;
|
|
||||||
border-bottom: 1px solid #cfe8fc;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p class="card-header-title" style="color: #205081">
|
|
||||||
🏷️ Tags do Projeto
|
|
||||||
</p>
|
|
||||||
<button class="card-header-icon" aria-label="more options">
|
|
||||||
<span class="icon" style="color: #205081"
|
|
||||||
><i class="fa-solid fa-angle-down"></i
|
|
||||||
></span>
|
|
||||||
</button>
|
|
||||||
</summary>
|
|
||||||
<div class="card-content" style="background-color: transparent">
|
|
||||||
{% for categoria in page.tags %} {% if categoria[1] and
|
|
||||||
categoria[1].size > 0 %}
|
|
||||||
<div class="mb-3">
|
|
||||||
<strong class="is-size-7 is-uppercase" style="color: #5b7da3"
|
|
||||||
>{{ categoria[0] }}:</strong
|
|
||||||
>
|
|
||||||
<div class="tags mt-2">
|
|
||||||
{% for valor in categoria[1] %} {% if valor != "" %} {% assign
|
|
||||||
tag_slug = valor | replace: ' ', '+' %} {% if categoria[0] ==
|
|
||||||
'bassline' %}
|
|
||||||
<a
|
|
||||||
href="{{ '/bassline/?bassline=' | append: tag_slug | relative_url }}"
|
|
||||||
class="tag is-link is-light"
|
|
||||||
>{{ valor }}</a
|
|
||||||
>
|
|
||||||
{% elsif categoria[0] == 'sample' %}
|
|
||||||
<a
|
|
||||||
href="{{ '/sample/?sample=' | append: tag_slug | relative_url }}"
|
|
||||||
class="tag is-link is-light"
|
|
||||||
>{{ valor }}</a
|
|
||||||
>
|
|
||||||
{% elsif categoria[0] == 'plugin' %}
|
|
||||||
<a
|
|
||||||
href="{{ '/plugin/?plugin=' | append: tag_slug | relative_url }}"
|
|
||||||
class="tag is-link is-light"
|
|
||||||
>{{ valor }}</a
|
|
||||||
>
|
|
||||||
{% elsif categoria[0] == 'automation' %}
|
|
||||||
<a
|
|
||||||
href="{{ '/automation/?automation=' | append: tag_slug | relative_url }}"
|
|
||||||
class="tag is-link is-light"
|
|
||||||
>{{ valor }}</a
|
|
||||||
>
|
|
||||||
{% else %}
|
|
||||||
<a
|
|
||||||
href="{{ '/' | append: tag_slug | relative_url }}"
|
|
||||||
class="tag is-link is-light"
|
|
||||||
>{{ valor }}</a
|
|
||||||
>
|
|
||||||
{% endif %} {% endif %} {% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %} {% endfor %}
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
{% endunless %} {% endif %} {% if page.tracks and page.tracks.size > 0
|
|
||||||
%}
|
|
||||||
<details
|
|
||||||
class="card mb-4"
|
|
||||||
open
|
|
||||||
style="
|
|
||||||
background-color: #f0f8ff;
|
|
||||||
border: 1px solid #cfe8fc;
|
|
||||||
border-radius: 8px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<summary
|
|
||||||
class="card-header"
|
|
||||||
style="
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #e1f0fa;
|
|
||||||
border-bottom: 1px solid #cfe8fc;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p class="card-header-title" style="color: #205081">
|
|
||||||
🎚️ Instrumentos & Padrões
|
|
||||||
</p>
|
|
||||||
<button class="card-header-icon" aria-label="more options">
|
|
||||||
<span class="icon" style="color: #205081"
|
|
||||||
><i class="fa-solid fa-angle-down"></i
|
|
||||||
></span>
|
|
||||||
</button>
|
|
||||||
</summary>
|
|
||||||
<div
|
|
||||||
class="card-content"
|
|
||||||
style="background-color: transparent; padding: 1.5rem"
|
|
||||||
>
|
|
||||||
{% for track in page.tracks %} {% if track.instruments and
|
|
||||||
track.instruments.size > 0 %}
|
|
||||||
<div
|
|
||||||
class="box p-0 shadow-sm"
|
|
||||||
style="
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #cfe8fc;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
height: fit-content !important;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="px-4 py-3"
|
|
||||||
style="
|
|
||||||
background-color: #eef6fc;
|
|
||||||
border-bottom: 1px solid #cfe8fc;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span class="tag is-link is-light" style="font-weight: 600"
|
|
||||||
>📂 {{ track.bassline_name }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-4" style="background-color: #fff">
|
|
||||||
{% for instrument in track.instruments %}
|
|
||||||
<div
|
|
||||||
class="instrument-wrapper box shadow-sm mb-4"
|
|
||||||
style="
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-left: 5px solid #3298dc;
|
|
||||||
padding: 1rem;
|
|
||||||
height: fit-content !important;
|
|
||||||
"
|
|
||||||
data-plugin-name="{{ instrument.instrument_name | downcase }}"
|
|
||||||
data-params="{{ instrument | jsonify | escape }}"
|
|
||||||
>
|
|
||||||
{% include _instrument_logic_inline.html %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% elsif track.instrument_name %} {% assign instrument = track %}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="instrument-wrapper box shadow-sm"
|
|
||||||
style="
|
|
||||||
border: 1px solid #cfe8fc;
|
|
||||||
border-left: 5px solid #3298dc;
|
|
||||||
padding: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
height: fit-content !important;
|
|
||||||
"
|
|
||||||
data-plugin-name="{{ instrument.instrument_name | downcase }}"
|
|
||||||
data-params="{{ instrument | jsonify | escape }}"
|
|
||||||
>
|
|
||||||
{% include _instrument_logic_inline.html %}
|
|
||||||
</div>
|
|
||||||
{% endif %} {% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column is-4">
|
|
||||||
<div
|
|
||||||
class="box has-background-info-light"
|
|
||||||
style="position: sticky; top: 20px"
|
|
||||||
>
|
|
||||||
<h3 class="title is-6 has-text-info mb-3">
|
|
||||||
🛠️ Abra na Criação Colaborativa
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{% assign creation_url = '/mmpSearch/creation.html?project=' | append:
|
|
||||||
page.file %}
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="{{ creation_url }}"
|
|
||||||
target="_blank"
|
|
||||||
class="button is-info is-fullwidth is-medium mb-4 shadow-sm"
|
|
||||||
>
|
|
||||||
<span>Abrir no MMPCreator</span>
|
|
||||||
<span class="icon"
|
|
||||||
><i class="fa-solid fa-up-right-from-square"></i
|
|
||||||
></span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<p class="is-size-7 has-text-grey-dark">
|
|
||||||
O link abrirá o projeto em uma nova aba para edição.
|
|
||||||
<br />
|
|
||||||
Arquivo: <code>{{ page.file }}</code>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="mt-4 is-hidden-mobile">
|
|
||||||
<hr style="background-color: #a4cbe0; height: 1px" />
|
|
||||||
<p class="title is-7 has-text-info-dark mb-2">Prévia do Editor:</p>
|
|
||||||
{% assign embed_url = creation_url | append: '&embed=true' %}
|
|
||||||
<iframe
|
|
||||||
src="{{ embed_url }}"
|
|
||||||
title="Prévia do Projeto"
|
|
||||||
style="
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #fff;
|
|
||||||
"
|
|
||||||
></iframe>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% capture instrument_logic %} {% assign plugin_name =
|
|
||||||
instrument.instrument_name | downcase %} {% assign is_sample = false %} {%
|
|
||||||
assign sample_src = "" %} {% if plugin_name == 'audiofileprocessor' or
|
|
||||||
instrument.audiofileprocessor %} {% assign is_sample = true %} {% assign
|
|
||||||
sample_src = instrument.audiofileprocessor.src | default: "" | strip %} {% endif
|
|
||||||
%} {% assign should_open = false %} {% if is_sample == false %} {% assign
|
|
||||||
should_open = true %} {% elsif is_sample == true and sample_src != "" %} {%
|
|
||||||
assign should_open = true %} {% endif %}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="instrument-wrapper mb-4 p-2"
|
|
||||||
style="border-left: 3px solid #3298dc; background: #fff"
|
|
||||||
data-plugin-name="{{ plugin_name }}"
|
|
||||||
data-params="{{ instrument | jsonify | escape }}"
|
|
||||||
>
|
|
||||||
<li style="list-style: none">
|
|
||||||
<details {% if should_open %}open{% endif %}>
|
|
||||||
<summary style="cursor: pointer; outline: none; margin-bottom: 0.5rem">
|
|
||||||
<div style="display: inline-flex; align-items: center; gap: 8px">
|
|
||||||
{% assign display_name = instrument.instrument_name %} {% if is_sample
|
|
||||||
%} {% if display_name contains "audiofileprocessor" and
|
|
||||||
instrument.patterns %} {% assign first_pattern_name =
|
|
||||||
instrument.patterns | map: 'name' | first %} {% if first_pattern_name
|
|
||||||
and first_pattern_name != empty %} {% assign display_name =
|
|
||||||
first_pattern_name | remove: ".ogg" | remove: ".ogg" | remove: ".flac"
|
|
||||||
| remove: ".mp3" %} {% elsif instrument.audiofileprocessor.src %} {%
|
|
||||||
assign src_parts = instrument.audiofileprocessor.src | split: '/' %}
|
|
||||||
{% assign file_name = src_parts | last %} {% assign display_name =
|
|
||||||
file_name | remove: ".ogg" | remove: ".ogg" | remove: ".flac" |
|
|
||||||
remove: ".mp3" %} {% endif %} {% endif %} {% endif %} {% assign
|
|
||||||
instrument_slug = display_name | replace: ' ', '+' %}
|
|
||||||
<a
|
|
||||||
href="{{ '/instruments/?instrument=' | append: instrument_slug | relative_url }}"
|
|
||||||
class="tag is-info is-light"
|
|
||||||
>
|
|
||||||
<strong>{{ display_name }}</strong>
|
|
||||||
{% unless is_sample %}
|
|
||||||
<span class="ml-1" style="font-size: 0.7em">(Synth 🎹)</span>{%
|
|
||||||
endunless %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
{% for pattern in instrument.patterns %} {% assign pattern_steps =
|
|
||||||
pattern.steps %} {% if pattern_steps and pattern_steps.size > 0 %}
|
|
||||||
<div
|
|
||||||
class="mt-2 ml-4"
|
|
||||||
style="display: flex; align-items: center; gap: 4px"
|
|
||||||
>
|
|
||||||
{% assign bassline_index = forloop.index | minus: 1 %} {% if
|
|
||||||
track.bassline_name %}
|
|
||||||
<span class="is-size-7 has-text-grey-light" style="min-width: 20px"
|
|
||||||
>{{ bassline_index }}:</span
|
|
||||||
>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="patterns-container"
|
|
||||||
style="display: flex; flex-direction: row; flex-wrap: wrap; gap: 4px"
|
|
||||||
>
|
|
||||||
{% assign total_steps = pattern_steps.size %} {% assign chunk_size = 4
|
|
||||||
%} {% assign num_chunks = total_steps | divided_by: chunk_size %} {%
|
|
||||||
assign remainder = total_steps | modulo: chunk_size %} {% if remainder
|
|
||||||
> 0 %}{% assign num_chunks = num_chunks | plus: 1 %}{% endif %} {% for
|
|
||||||
i in (0..num_chunks) %} {% assign start_index = i | times: chunk_size
|
|
||||||
%} {% assign current_chunk_array = pattern_steps | slice: start_index,
|
|
||||||
chunk_size %} {% if current_chunk_array.size > 0 %} {% assign
|
|
||||||
chunk_string = "" %} {% for step in current_chunk_array %} {% if step
|
|
||||||
== true or step == 'true' or step == 1 %} {% assign chunk_string =
|
|
||||||
chunk_string | append: '1' %} {% else %} {% assign chunk_string =
|
|
||||||
chunk_string | append: '0' %} {% endif %} {% endfor %} {% assign
|
|
||||||
search_url = '/pattern/?p=' | append: chunk_string | relative_url %}
|
|
||||||
<a
|
|
||||||
href="{{ search_url }}"
|
|
||||||
title="Buscar padrão {{ chunk_string }}"
|
|
||||||
style="display: inline-block"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 2px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: #fff;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{% for step_active in current_chunk_array %} {% assign step_color
|
|
||||||
= '#e0e0e0' %} {% if step_active == true or step_active == 'true'
|
|
||||||
or step_active == 1 %} {% assign step_color = '#4caf50' %} {%
|
|
||||||
endif %}
|
|
||||||
<div
|
|
||||||
style="width: 5px; height: 10px; background-color: {{ step_color }}; border-radius: 1px; margin-right: 1px;"
|
|
||||||
></div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{% endif %} {% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %} {% endfor %}
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<div class="playback-controls mt-2 pl-4">
|
|
||||||
{% if is_sample %} {% if sample_src != "" %} {% assign
|
|
||||||
audio_filename_with_path = 'src/samples/' | append: sample_src %}
|
|
||||||
<audio
|
|
||||||
controls
|
|
||||||
class="js-sample-player"
|
|
||||||
style="height: 30px; width: 250px"
|
|
||||||
>
|
|
||||||
<source
|
|
||||||
src="{{ audio_filename_with_path | relative_url }}"
|
|
||||||
type="audio/ogg"
|
|
||||||
/>
|
|
||||||
{% assign ogg_path = audio_filename_with_path %}
|
|
||||||
<source src="{{ ogg_path | relative_url }}" type="audio/ogg" />
|
|
||||||
</audio>
|
|
||||||
{% else %}
|
|
||||||
<p class="has-text-danger is-size-7">O sample **não foi enviado** 😢</p>
|
|
||||||
{% endif %} {% else %}
|
|
||||||
<button class="button is-small is-primary is-outlined js-play-synth-btn">
|
|
||||||
<span class="icon is-small"><i class="fa-solid fa-play"></i></span>
|
|
||||||
<span>Testar Plugin</span>
|
|
||||||
</button>
|
|
||||||
<span class="is-size-7 has-text-grey ml-2">Via Web Audio API</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</div>
|
|
||||||
{% endcapture %}
|
|
||||||
|
|
||||||
<div id="preview-modal" class="modal">
|
|
||||||
<div class="modal-background"></div>
|
|
||||||
<div
|
|
||||||
class="modal-card"
|
|
||||||
style="width: 90%; max-width: 900px; height: 80vh; max-height: 800px"
|
|
||||||
>
|
|
||||||
<header
|
|
||||||
class="modal-card-head"
|
|
||||||
style="
|
|
||||||
background-color: #f0f8ff;
|
|
||||||
border-bottom: 1px solid #cfe8fc;
|
|
||||||
padding: 10px 20px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
class="modal-card-title is-size-6"
|
|
||||||
id="modal-title"
|
|
||||||
style="color: #205081; font-weight: bold; margin-bottom: 0"
|
|
||||||
>
|
|
||||||
Preview
|
|
||||||
</p>
|
|
||||||
<button class="delete" aria-label="close"></button>
|
|
||||||
</header>
|
|
||||||
<section
|
|
||||||
class="modal-card-body p-0"
|
|
||||||
style="background-color: #000; display: flex"
|
|
||||||
>
|
|
||||||
<iframe
|
|
||||||
id="preview-iframe"
|
|
||||||
src=""
|
|
||||||
style="width: 100%; height: 100%; border: none; flex: 1"
|
|
||||||
></iframe>
|
|
||||||
</section>
|
|
||||||
<footer
|
|
||||||
class="modal-card-foot"
|
|
||||||
style="
|
|
||||||
justify-content: flex-end;
|
|
||||||
background-color: #fff;
|
|
||||||
border-top: 1px solid #cfe8fc;
|
|
||||||
padding: 10px 20px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<button class="button is-small" id="close-modal-btn">Fechar</button>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
id="full-edit-btn"
|
|
||||||
target="_blank"
|
|
||||||
class="button is-small is-info"
|
|
||||||
>
|
|
||||||
<span>Abrir Editor Completo</span>
|
|
||||||
<span class="icon is-small ml-1"
|
|
||||||
><i class="fa-solid fa-up-right-from-square"></i
|
|
||||||
></span>
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
// --- LÓGICA DO MODAL DE PREVIEW ---
|
|
||||||
const modal = document.getElementById("preview-modal");
|
|
||||||
const iframe = document.getElementById("preview-iframe");
|
|
||||||
const modalTitle = document.getElementById("modal-title");
|
|
||||||
const fullEditBtn = document.getElementById("full-edit-btn");
|
|
||||||
const closeButtons = document.querySelectorAll(
|
|
||||||
".modal-background, .modal-card-head .delete, #close-modal-btn"
|
|
||||||
);
|
|
||||||
|
|
||||||
function openModal(title, embedUrl) {
|
|
||||||
if (!modal) return;
|
|
||||||
modalTitle.textContent = "Espiando: " + title;
|
|
||||||
iframe.src = embedUrl;
|
|
||||||
// Ajusta botão de editor completo
|
|
||||||
const fullUrl = embedUrl.replace("&embed=true", "");
|
|
||||||
fullEditBtn.href = fullUrl;
|
|
||||||
|
|
||||||
modal.classList.add("is-active");
|
|
||||||
document.documentElement.classList.add("is-clipped");
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
if (!modal) return;
|
|
||||||
modal.classList.remove("is-active");
|
|
||||||
document.documentElement.classList.remove("is-clipped");
|
|
||||||
iframe.src = ""; // Limpa iframe
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger nos botões
|
|
||||||
document.querySelectorAll(".js-open-modal").forEach((btn) => {
|
|
||||||
btn.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const title = btn.dataset.projectTitle;
|
|
||||||
const url = btn.dataset.embedUrl;
|
|
||||||
openModal(title, url);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fechar
|
|
||||||
closeButtons.forEach((el) => el.addEventListener("click", closeModal));
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
if (e.key === "Escape") closeModal();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { InstrumentFactory } from '{{ "/assets/js/audio/InstrumentFactory.js" | relative_url }}';
|
import { InstrumentFactory } from '{{ "/assets/js/audio/InstrumentFactory.js" | relative_url }}';
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const factory = new InstrumentFactory();
|
const factory = new InstrumentFactory();
|
||||||
const synthButtons = document.querySelectorAll(".js-play-synth-btn");
|
const params = new URLSearchParams(window.location.search);
|
||||||
synthButtons.forEach((btn) => {
|
const projectId = params.get("id");
|
||||||
btn.addEventListener("click", (e) => {
|
|
||||||
const wrapper = e.target.closest(".instrument-wrapper");
|
if (!projectId) {
|
||||||
if (wrapper && wrapper.dataset.params) {
|
window.location.href = '{{ "/projetos/" | relative_url }}';
|
||||||
try {
|
return;
|
||||||
const instrumentData = JSON.parse(wrapper.dataset.params);
|
|
||||||
factory.play(instrumentData);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro audio:", error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
'{{ "/mmpSearch/src_mmpSearch/saida_analises/db_final_completo.json" | relative_url }}',
|
||||||
|
)
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((data) => {
|
||||||
|
const project = data.find(
|
||||||
|
(p) =>
|
||||||
|
p.file === projectId ||
|
||||||
|
p.arquivo === projectId + ".ogg" ||
|
||||||
|
p.arquivo === projectId,
|
||||||
|
);
|
||||||
|
if (!project) {
|
||||||
|
document.getElementById("project-loader").innerHTML =
|
||||||
|
"<h1 class='title'>Projeto não encontrado 😢</h1>";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
renderProjectPage(project, factory);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function renderProjectPage(p, factory) {
|
||||||
|
const header = document.getElementById("project-header");
|
||||||
|
const main = document.getElementById("main-content");
|
||||||
|
const sidebar = document.getElementById("sidebar-content");
|
||||||
|
|
||||||
|
// 1. Render Header
|
||||||
|
header.innerHTML = `
|
||||||
|
<div style="display: flex; align-items: center; gap: 15px">
|
||||||
|
<h1 class="title is-5 m-0"><code>${p.file || p.arquivo}</code></h1>
|
||||||
|
${p.bpm ? `<span class="tag is-dark">🎵 ${p.bpm}</span>` : ""}
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 15px">
|
||||||
|
<audio controls style="height: 25px;">
|
||||||
|
<source src="{{ '/src_mmpSearch/wav/' | relative_url }}${p.file}.ogg" type="audio/ogg">
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 2. Render Tags
|
||||||
|
let tagsHtml = "";
|
||||||
|
if (p.tags) {
|
||||||
|
tagsHtml = `<details class="card mb-4" open style="background-color: #f0f8ff; border: 1px solid #cfe8fc; border-radius: 8px;">
|
||||||
|
<summary class="card-header" style="cursor: pointer; background-color: #e1f0fa;"><p class="card-header-title">🏷️ Tags</p></summary>
|
||||||
|
<div class="card-content">
|
||||||
|
${Object.entries(p.tags)
|
||||||
|
.map(
|
||||||
|
([cat, values]) => `
|
||||||
|
<div class="mb-3">
|
||||||
|
<strong class="is-size-7 is-uppercase" style="color: #5b7da3">${cat}:</strong>
|
||||||
|
<div class="tags mt-2">${values.map((v) => `<span class="tag is-link is-light">${v}</span>`).join("")}</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
</details>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Render Tracks/Instruments
|
||||||
|
const tracksHtml = `
|
||||||
|
<details class="card mb-4" open style="background-color: #f0f8ff; border: 1px solid #cfe8fc; border-radius: 8px;">
|
||||||
|
<summary class="card-header" style="cursor: pointer; background-color: #e1f0fa;"><p class="card-header-title">🎚️ Instrumentos</p></summary>
|
||||||
|
<div class="card-content">
|
||||||
|
${(p.tracks || [])
|
||||||
|
.map(
|
||||||
|
(track) => `
|
||||||
|
<div class="box mb-4" style="border-left: 5px solid #3298dc;">
|
||||||
|
<span class="tag is-link is-light mb-2">📂 ${track.bassline_name || "Track"}</span>
|
||||||
|
${(track.instruments || [track])
|
||||||
|
.map(
|
||||||
|
(inst) => `
|
||||||
|
<div class="instrument-wrapper p-2 mb-2" style="background: #fff; border-radius: 4px;">
|
||||||
|
<strong>${inst.instrument_name}</strong>
|
||||||
|
<div class="mt-2">${renderPatternGrid(inst.patterns)}</div>
|
||||||
|
<button class="button is-small is-primary is-outlined mt-2 js-play-synth" data-params='${JSON.stringify(inst)}'>Testar Plugin</button>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
`;
|
||||||
|
|
||||||
|
main.innerHTML = tagsHtml + tracksHtml;
|
||||||
|
|
||||||
|
// 4. Sidebar Editor
|
||||||
|
const creationUrl = `{{ '/mmpSearch/creation.html?project=' | relative_url }}${p.file}`;
|
||||||
|
sidebar.innerHTML = `
|
||||||
|
<div class="box has-background-info-light" style="position: sticky; top: 20px">
|
||||||
|
<h3 class="title is-6 has-text-info">🛠️ MMPCreator</h3>
|
||||||
|
<a href="${creationUrl}" target="_blank" class="button is-info is-fullwidth mb-4">Abrir no Editor</a>
|
||||||
|
<iframe src="${creationUrl}&embed=true" style="width: 100%; height: 300px; border-radius: 4px; background: #fff;"></iframe>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById("project-loader").classList.add("is-hidden");
|
||||||
|
document.getElementById("project-content").classList.remove("is-hidden");
|
||||||
|
|
||||||
|
// Event Listeners para Áudio
|
||||||
|
document.querySelectorAll(".js-play-synth").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () =>
|
||||||
|
factory.play(JSON.parse(btn.dataset.params)),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPatternGrid(patterns) {
|
||||||
|
if (!patterns) return "";
|
||||||
|
return patterns
|
||||||
|
.map((p) => {
|
||||||
|
const steps = p.steps || [];
|
||||||
|
return `<div style="display: flex; gap: 2px; margin-bottom: 4px;">
|
||||||
|
${steps.map((s) => `<div style="width: 6px; height: 12px; background: ${s ? "#4caf50" : "#e0e0e0"}; border-radius: 1px;"></div>`).join("")}
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
1062
pages/projetos.html
1062
pages/projetos.html
File diff suppressed because it is too large
Load Diff
77
readme.md
77
readme.md
|
|
@ -16,67 +16,80 @@ O sistema é um ecossistema completo para mineração, submissão, processamento
|
||||||
|
|
||||||
O script `crawler.py` atua como um bot de mineração para popular a base de dados da plataforma baixando projetos diretamente do repositório oficial (LMMS Sharing Platform - `lmms.io/lsp/`).
|
O script `crawler.py` atua como um bot de mineração para popular a base de dados da plataforma baixando projetos diretamente do repositório oficial (LMMS Sharing Platform - `lmms.io/lsp/`).
|
||||||
|
|
||||||
- **Segurança e Estabilidade:** O crawler é multiprocessado (limitado a 4 workers) e possui um sistema de _throttle_ que pausa a execução caso a memória RAM livre do servidor caia abaixo de 500MB.
|
- **Segurança e Estabilidade:** O crawler é multiprocessado e possui um sistema de _throttle_ que pausa a execução caso a memória RAM livre do servidor caia abaixo de 500MB.
|
||||||
- **Sanitização e Auditoria:** Os arquivos baixados têm seus nomes sanitizados (slugify) e o processo gera um arquivo de auditoria (`audit_crawler.csv`) registrando tamanho, velocidade e status de cada download.
|
- **Sanitização e Auditoria:** Utiliza `BeautifulSoup4` para extração de links e gera relatórios de performance em CSV.
|
||||||
|
|
||||||
### 2. Pipeline ETL e Motor de Renderização
|
### 2. Pipeline ETL e Motor de Renderização
|
||||||
|
|
||||||
A conversão dos arquivos nativos em artefatos web é orquestrada pelo script `pipeline_etl.sh`, que executa três etapas sequenciais:
|
A conversão dos arquivos nativos em artefatos web é orquestrada pelo script `pipeline_etl.sh`, executando o processamento em lotes:
|
||||||
|
|
||||||
1. **Extração e Renderização (`handler/main.py`):** O script utiliza a biblioteca `multiprocessing` e processa os projetos em lotes. Ele invoca o binário do LMMS em modo `offscreen` (Headless) para exportar o áudio gerado nativamente em `.ogg` e converte a estrutura do `.mmp` em dicionários de dados (JSON/YAML).
|
1. **Extração e Renderização (`handler/main.py`):** Invoca o LMMS em modo `offscreen` para gerar áudio e converte o XML `.mmp` em JSON/YAML.
|
||||||
2. **Análise de Áudio (`classificacao/analise_audio.py`):** Processamento das características sonoras.
|
2. **Análise e Inteligência Artificial:** Utiliza a biblioteca **Essentia (Tensorflow)** para extração de características acústicas e classificação de áudio.
|
||||||
3. **Classificação Mestre (`classificacao/classificacao_mestre.py`):** Organização final e indexação para o frontend.
|
|
||||||
|
|
||||||
### 3. MMPSearch (Catálogo Estático Frontend)
|
---
|
||||||
|
|
||||||
Biblioteca gerada via Jekyll a partir dos arquivos JSON/YAML exportados pelo Pipeline ETL, focada em leveza, indexação e busca de projetos, com renderização de áudio via Web Audio API.
|
## 📦 Ecossistema Python e Dependências (venv)
|
||||||
|
|
||||||
### 4. MMPCreator (Colaboração em Tempo Real)
|
O ambiente virtual (`venv`) contém as bibliotecas necessárias para o ciclo de vida do dado. As principais dependências instaladas são:
|
||||||
|
|
||||||
Sequenciador interativo. Utiliza WebSockets para sincronizar edições simultâneas (áudio e MIDI) entre múltiplos usuários em salas virtuais.
|
### Web & API Framework
|
||||||
|
|
||||||
|
- **Flask (v3.1.2):** Engine principal da API.
|
||||||
|
- **Flask-SocketIO:** Suporte a comunicação bi-direcional via WebSockets.
|
||||||
|
- **Gunicorn:** Servidor HTTP WSGI para produção.
|
||||||
|
- **Flask-Admin & WTForms:** Interface de gerenciamento e validação de formulários.
|
||||||
|
|
||||||
|
### Segurança e Persistência
|
||||||
|
|
||||||
|
- **SQLAlchemy (v2.0):** ORM para manipulação do banco `users.db`.
|
||||||
|
- **Flask-Bcrypt & Cryptography:** Hashing de senhas e camadas de criptografia.
|
||||||
|
|
||||||
|
### Processamento e Ciência de Dados
|
||||||
|
|
||||||
|
- **Essentia-Tensorflow:** Biblioteca avançada para análise de áudio e music information retrieval (MIR).
|
||||||
|
- **NumPy:** Suporte para arrays multidimensionais e cálculos matemáticos pesados.
|
||||||
|
- **PyYAML:** Geração de metadados para integração com o Jekyll.
|
||||||
|
|
||||||
|
### Utilidades e Monitoramento
|
||||||
|
|
||||||
|
- **Psutil:** Monitoramento de hardware para controle de processos paralelos.
|
||||||
|
- **Watchdog:** Monitoramento de eventos no sistema de arquivos.
|
||||||
|
- **tqdm:** Barras de progresso para tarefas de longa duração no terminal.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚙️ Serviços de Hospedagem (Produção)
|
## ⚙️ Serviços de Hospedagem (Produção)
|
||||||
|
|
||||||
A infraestrutura em produção é orquestrada por gerenciadores de serviço do Linux (`systemd`), garantindo que a API e o servidor de WebSockets estejam sempre no ar.
|
A infraestrutura é mantida via `systemd` para garantir alta disponibilidade.
|
||||||
|
|
||||||
### Servidor de Upload, Autenticação e Build (Flask/Gunicorn)
|
### API de Upload e Processamento (Gunicorn)
|
||||||
|
|
||||||
Serviço responsável por receber arquivos, gerenciar a sessão dos usuários (SQLite, Bcrypt, Flask-Login) e automatizar o build do site via threads em segundo plano.
|
- **Serviço:** `upload_mmpSearch.service`
|
||||||
|
- **Porta:** 33002
|
||||||
- **Serviço:** `/etc/systemd/system/upload_mmpSearch.service`
|
- **Timeout:** 300s (Essencial para o tempo de renderização do LMMS)
|
||||||
- **Tecnologia:** Gunicorn servindo a aplicação Flask (`upload_server:app`).
|
|
||||||
- **Configuração:** Roda com o usuário `www-data`. Utiliza 3 _workers_ e possui um timeout estendido de 300 segundos, o que é vital para não derrubar a conexão durante renderizações pesadas de áudio.
|
|
||||||
- **Acesso da API:** `http://0.0.0.0:33002`
|
|
||||||
|
|
||||||
### Backend de Colaboração (NodeJS)
|
### Backend de Colaboração (NodeJS)
|
||||||
|
|
||||||
Hospeda a lógica de WebSockets responsável por gerenciar as salas e a colaboração online do editor MMPCreator em tempo real.
|
- **Serviço:** `backend-MMPCreator.service`
|
||||||
|
- **Porta:** 33001
|
||||||
- **Serviço:** `/etc/systemd/system/backend-MMPCreator.service`
|
- **Tecnologia:** Sincronização via Socket.io com SSL nativo.
|
||||||
- **Tecnologia:** NodeJS (`server.js`).
|
|
||||||
- **Configuração de Segurança:** Roda sob o usuário `www-data` e possui acesso direto aos certificados Let's Encrypt via variáveis de ambiente (`SSL_FULLCHAIN` e `SSL_PRIVKEY`). Essa abordagem elimina a necessidade do Apache atuar como proxy reverso, facilitando a comunicação direta e segura via `wss://` e `https://`.
|
|
||||||
- **Comportamento:** Configurado com `Restart=always` para garantir alta disponibilidade.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ Área de Desenvolvimento (Execução Manual)
|
## 🛠️ Área de Desenvolvimento (Execução Manual)
|
||||||
|
|
||||||
Para testes ou execuções manuais, o site está sendo desenvolvido em um ambiente virtual Python (`venv`).
|
1. Ative o ambiente virtual:
|
||||||
|
|
||||||
1. Ative o ambiente:
|
|
||||||
`source venv/bin/activate`
|
`source venv/bin/activate`
|
||||||
|
|
||||||
2. Para inicializar a biblioteca de samples do MMPCreator:
|
2. Execução do Pipeline ETL completo:
|
||||||
`cd scripts/creations && python generateManifest.py`
|
|
||||||
|
|
||||||
3. Para forçar a indexação e execução manual do pipeline ETL:
|
|
||||||
`cd scripts && ./pipeline_etl.sh`
|
`cd scripts && ./pipeline_etl.sh`
|
||||||
|
|
||||||
|
3. Inicialização de Manifestos:
|
||||||
|
`cd scripts/creations && python generateManifest.py`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✅ Funcionalidades Já Implementadas
|
## ✅ Funcionalidades Implementadas
|
||||||
|
|
||||||
- [x] Web Crawler automatizado com auditoria de hardware.
|
- [x] Web Crawler automatizado com auditoria de hardware.
|
||||||
- [x] Processamento de arquivos LMMS de forma _headless_ (renderização de áudio em servidor sem interface gráfica).
|
- [x] Processamento de arquivos LMMS de forma _headless_ (renderização de áudio em servidor sem interface gráfica).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
bcrypt==5.0.0
|
||||||
|
beautifulsoup4==4.14.3
|
||||||
|
bidict==0.23.1
|
||||||
|
blinker==1.9.0
|
||||||
|
bs4==0.0.2
|
||||||
|
certifi==2025.10.5
|
||||||
|
cffi==2.0.0
|
||||||
|
charset-normalizer==3.4.4
|
||||||
|
click==8.3.1
|
||||||
|
cryptography==46.0.3
|
||||||
|
essentia-tensorflow==2.1b6.dev1389
|
||||||
|
Flask==3.1.2
|
||||||
|
Flask-Admin==2.0.2
|
||||||
|
Flask-Bcrypt==1.0.1
|
||||||
|
flask-cors==6.0.1
|
||||||
|
Flask-Login==0.6.3
|
||||||
|
Flask-SocketIO==5.5.1
|
||||||
|
Flask-SQLAlchemy==3.1.1
|
||||||
|
gevent==25.9.1
|
||||||
|
greenlet==3.2.4
|
||||||
|
gunicorn==23.0.0
|
||||||
|
h11==0.16.0
|
||||||
|
idna==3.11
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
Jinja2==3.1.6
|
||||||
|
MarkupSafe==3.0.3
|
||||||
|
numpy==2.3.5
|
||||||
|
packaging==25.0
|
||||||
|
psutil==7.2.2
|
||||||
|
pycparser==2.23
|
||||||
|
pyOpenSSL==25.3.0
|
||||||
|
python-engineio==4.12.3
|
||||||
|
python-socketio==5.14.3
|
||||||
|
PyYAML==6.0.3
|
||||||
|
requests==2.32.5
|
||||||
|
simple-websocket==1.1.0
|
||||||
|
six==1.17.0
|
||||||
|
soupsieve==2.8.3
|
||||||
|
SQLAlchemy==2.0.44
|
||||||
|
tqdm==4.67.1
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
urllib3==2.5.0
|
||||||
|
watchdog==6.0.0
|
||||||
|
Werkzeug==3.1.4
|
||||||
|
wsproto==1.2.0
|
||||||
|
WTForms==3.2.1
|
||||||
|
zope.event==6.1
|
||||||
|
zope.interface==8.0.1
|
||||||
Loading…
Reference in New Issue