Editor de Notação Musical - Refatorando a lógica de criação da vista em outro lugar

5

Deixe-me começar dizendo que conhecer alguma teoria da música elementar e notação musical pode ser útil para entender o problema em questão.

Atualmente, estou criando um editor de notação musical e tablatura (em JavaScript). Mas cheguei a um ponto em que as partes centrais do programa estão mais ou menos lá. Todas as funcionalidades que pretendo adicionar neste momento vão realmente construir a base que criei. Como resultado, quero refatorar para realmente solidificar meu código.

Estou usando uma API chamada VexFlow para renderizar a notação. Basicamente eu passo as partes do estado do editor para o VexFlow para construir a representação gráfica da partitura.

(EDIT: O diagrama a seguir está desatualizado como eu comecei a retrabalhar a estrutura, por favor veja a atualização na parte inferior do post para o novo diagrama) Aqui está um diagrama UML aproximado e despojado que mostra o esboço do meu programa:

Em essência, um Part tem muitos Measure s que tem muitos Note s que tem muitos NoteItem s (sim, isso é semanticamente estranho, já que um acorde é representado como Note com múltiplos NoteItem s, posições individuais ou posições dos trastes). Todos os relacionamentos são bidirecionais.

Existem alguns problemas com o meu design porque minha classe Measure contém a maioria da lógica inteira de visualização do aplicativo.

  1. A classe contém os dados sobre todos os objetos do VexFlow (a representação gráfica da partitura). Ele contém o objeto Staff gráfico e as notas gráficas. (Eles não deveriam ser colocados em outro lugar no programa?)

  2. Enquanto VexFlowFactory lida com a criação real (e algum processamento) da maioria dos objetos VexFlow, Measure ainda "direciona" a criação de todos os objetos e em que ordem eles devem ser criados tanto o VexFlowStaff quanto o VexFlowNotes.

Não estou procurando uma resposta específica, pois você precisa de um entendimento muito mais profundo do meu código. Apenas uma direção geral para entrar.

Aqui está uma ideia, crie uma MeasureView / NoteView / PartView classes que contém os objetos VexFlow básicos para cada classe, além de qualquer lógica estranha para sua criação? mas onde essas visões seriam contidas? Eu crio um ScoreView que é uma representação gráfica paralela de tudo? Para que ScoreView.render() caísse em cascata em PartView e chamasse render para cada PartView e descesse para cada MeasureView , etc. Novamente, não tenho a menor ideia de que direção entrar. Quanto mais eu penso sobre isso, mais maneiras de ir parecem aparecer na minha cabeça.

Eu tentei ser o mais conciso e simplista possível, ao mesmo tempo em que resolvo o problema. Por favor, sinta-se à vontade para me perguntar se alguma coisa não estiver clara. É uma grande dificuldade tentar simplificar um problema complicado em suas partes centrais.

ATUALIZAÇÃO: ------------------------------------------ --------------------------------

Então eu fui em frente e comecei a refatorar com a minha ideia de cima. Aqui está um diagrama atualizado

@ JW01 Eu entendo por que relacionamentos bidirecionais são potencialmente ruins (difíceis de manter, aumentar a complexidade), mas acredito que a flexibilidade aumentada vale a pena e a implementei de maneira sustentável. Cada notação "contêiner" ( Score , Part , Measure , Note - todos contêm filhos de algum tipo) herda de Container (essa relação não é mostrada no diagrama porque o programa que uso é merda e parece super confuso). O método Container.addItem(item) mistura os métodos de classe Traversable e parent com o item adicionado.

Para mim, isso pareceu muito inteligente e fácil de entender. Basicamente, qualquer item colocado em um contêiner obtém funcionalidade estendida. Como mencionado em um comentário anterior, talvez eu queira extrair uma boa API de dados de música, o que permitiria que "plug-ins" fossem criados facilmente. Atravessar em ambas as direções seria particularmente útil para funcionalidades como essa. Além disso, essas classes são pequenas e fáceis de manter e improváveis de mudar.

No entanto, entendo que quanto menos coisas dependerem dessas relações, melhor. Então, é definitivamente melhor isolar essa funcionalidade de deslocamento para uso com o módulo Selection .

Estou percebendo que minhas classes View estão fazendo muito agora. Criação, renderização, formatação e manipulação de cliques. Provavelmente seria sensato fatorar um módulo abstrato de View composto por um ViewBuilder, ViewFormatter, ViewRenderer, ClickBehavior?

    
por Cyril Silverman 07.11.2012 / 21:44
fonte

1 resposta

2

Parece que você deu como certo que seu layout de classes deveria ser modelado em como os humanos percebem partituras. Enquanto olhamos para uma partitura e pensamos nela como dividida em partes gerenciáveis chamadas de medidas, um computador não precisa necessariamente fazer isso. Ele pode simplesmente perceber a música como um longo fluxo de notas, com algumas linhas colocadas em intervalos iguais (por alguma medida) entre / entre as notas.

Quando eu olhei para o seu layout, a primeira coisa que surgiu na minha cabeça foi: como ele lida com duas notas abrangendo duas medidas que estão amarradas?

Você poderia pensar nas barras de medidas como um tipo de "nota", do ponto de vista do código. Uma vez que as medidas são geralmente iguais e suas posições podem ser mais implicadas do resto da música, você poderia colocar uma "nota" para marcar o início da primeira medida e declarar a fórmula de compasso, e as medidas de subsequência podem ser calculadas -a-mosca a partir daí. Então, se você quiser fazer algo extravagante como colocar uma medida fora da assinatura (uma medida com 2 batidas em uma música 4/4, por exemplo) ou uma mudança de assinatura, você coloca outra "nota" onde o evento acontece / começa.

Além disso, eu (principalmente) sei ler partituras, mas não usei nenhum software de edição de partituras interativas, então não tenho muita base para fazer inferências sobre a adequação de qualquer abordagem em particular. Considere tentar obter alguma experiência usando software de edição de pontuação de reputação, para se inspirar. Também esteja preparado para jogar fora seu primeiro protótipo - pode ser que a única maneira de descobrir o que você está fazendo de errado seja fazer errado primeiro:)

    
por 14.11.2012 / 01:37
fonte