-
-
Save GanaramInukshuk/e18439e300dba0bb59bd0211ffbf2932 to your computer and use it in GitHub Desktop.
Mosfinder for VBA but refined further to include a version that skips step visualization and lists the steps as a sequence of numbers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
' Mosfinder for VBA | |
' By gdinuk | |
Sub MosFinder() | |
' | |
' Macro1 Macro | |
' Denote the starting cell as B2 | |
Dim rowIndex As Integer | |
Dim colIndex As Integer | |
rowIndex = 2 | |
colIndex = 2 | |
' Make B2 the active cell | |
'Cells(rowIndex, colIndex). | |
' Get the value of ed from cell B2 | |
Dim ed As Integer | |
ed = ActiveSheet.Cells(rowIndex, colIndex).Value | |
MsgBox ("Generating scale table for " & ed & " equal divisions of the octave.") | |
'Dim largeStep As Integer | |
'Dim smallStep As Integer | |
'largeStep = 7 | |
'smallStep = ed - largeStep | |
Dim progenitorScale() As Variant | |
rowIndex = rowIndex + 1 | |
'Call GenerateTable(ed, 1, rowIndex, colIndex) | |
' This for loop will generate every possible scale for every possible number of | |
' periods, starting with single-period scales and working its way up through the | |
' factors of ed, should there be any. | |
' If this is allowed to loop to ed/2, then the generator pair will start as 1\ed | |
' and 1\ed. To eliminate this scale, subtract 1 from the loop limit. | |
For i = 1 To (ed / 2) - 1 | |
Dim period As Integer | |
period = i | |
Call GenerateTable(ed, period, rowIndex, colIndex, True) | |
Next i | |
' Move the active cell | |
Set cellToMoveTo = Cells(2, 2) | |
cellToMoveTo.Select | |
End Sub | |
Sub MosFinderWithoutVisualization() | |
' Denote the starting cell as B2 | |
Dim rowIndex As Integer | |
Dim colIndex As Integer | |
rowIndex = 2 | |
colIndex = 2 | |
' Make B2 the active cell | |
'Cells(rowIndex, colIndex). | |
' Get the value of ed from cell B2 | |
Dim ed As Integer | |
ed = ActiveSheet.Cells(rowIndex, colIndex).Value | |
MsgBox ("Generating scale table for " & ed & " equal divisions of the octave.") | |
'Dim largeStep As Integer | |
'Dim smallStep As Integer | |
'largeStep = 7 | |
'smallStep = ed - largeStep | |
Dim progenitorScale() As Variant | |
rowIndex = rowIndex + 1 | |
'Call GenerateTable(ed, 1, rowIndex, colIndex) | |
' This for loop will generate every possible scale for every possible number of | |
' periods, starting with single-period scales and working its way up through the | |
' factors of ed, should there be any. | |
' If this is allowed to loop to ed/2, then the generator pair will start as 1\ed | |
' and 1\ed. To eliminate this scale, subtract 1 from the loop limit. | |
For i = 1 To (ed / 2) - 1 | |
Dim period As Integer | |
period = i | |
Call GenerateTable(ed, period, rowIndex, colIndex, False) | |
Next i | |
' Move the active cell | |
Set cellToMoveTo = Cells(2, 2) | |
cellToMoveTo.Select | |
End Sub | |
' Generates the table for a given ed and period, starting with the header. | |
' This requires a row/column offset, and those are changed as the table is made | |
' The header cells are as follows: | |
' - Step pattern: spans ed cells and denotes the step pattern for each scale | |
' - General information: spans 3 cells and denotes temperament-agnostic information (mos, step ratio, and tamnams name) | |
' - Temperament information: spans 1 cell (minimum) and denotes information related to temperaments | |
' Parameters are as follows: | |
' - ed: the number of equal divisions | |
' - period: if ed is divisible by period, then the table produced will be a multi-period scale; | |
' the subroutine shouldn't make a table if ed isn't divisible by period | |
' - rowIndex and colIndex: the cell that the table will start at denoted by row/column indices | |
Public Sub GenerateTable(ByRef ed As Integer, ByRef periods As Integer, ByRef rowIndex As Integer, ByRef colIndex As Integer, ByRef useStepVisualization As Boolean) | |
' Determine whether ed is divisible by period | |
Dim remainder As Integer | |
remainder = ed Mod periods | |
' If ed is not divisible by period, exit the subroutine early | |
If remainder <> 0 Then | |
Exit Sub | |
End If | |
' A period of ed/2 will create a scale whose generator pair is 1\ed and 1\ed. | |
' The resulting scale will be an equal division, and will be the only scale | |
' in the table. If all these conditions are true, that scale will be made anyway: | |
' - constIncludeGeneratorPairsThatAreTheSame | |
' - constIncludeEqualDivisionScales | |
' Otherwise, that scale will not be generated, and so making an otherwise empty | |
' table won't be necessary, so none of the headers are needed either. | |
If (period * 2) = ed Then | |
Exit Sub | |
End If | |
' Preliminary header information | |
' There are three levels of headers | |
' - Header1 is the title of the entire table and spans the entire table | |
' - Header2 is for scale information, and the columns are step information, mos, step ratio, | |
' tamnams name, and temperament. Step information either spans ed columns or 1 column, | |
' depending on whether a step visualization is desired. | |
' There will be a minimum of 5 columns, and at most, 5 + ed - 1 cols. | |
' Since the number of step cols is variable, the preliminary header titles here are for the | |
' other four column headers. The step header is generated separately and these are appended | |
' at the end of the others. | |
' Cell widths are also set here | |
Dim preliminaryHeader1Titles() As Variant | |
Dim preliminaryHeader2Titles() As Variant | |
Dim preliminaryColWidths() As Variant | |
preliminaryHeader1Titles = Array("", "", "", "") | |
preliminaryHeader2Titles = Array("Mos", "Step Ratio", "TAMNAMS Name", "Temperament") | |
preliminaryColWidths = Array(12, 12, 24, 24) | |
' Generate the header information | |
Dim header1Titles() As Variant | |
Dim header2Titles() As Variant | |
Dim colWidths() As Variant | |
' First populate the arrays with the titles | |
' It should be noted that the title of header1 and the title of step information | |
' is dependent on period count and generator pair respectively. As such, these | |
' header titles will have to be calculated first, and in the case of header2, | |
' recauculated for every generator pair. | |
If periods = 1 Then | |
header1Titles = Array("Single-Period Scales for " & ed & " Equal Division of the Octave") | |
Else | |
header1Titles = Array("Multi-Period Scales (periods = " & periods & ") for " & ed & " Equal Division of the Octave") | |
End If | |
header2Titles = Array("step_info_overwrite_with_generator_pair") | |
' If step visualization is used, then each column in the step visualization thing is 2 units | |
' otherwise, it's ed * 2 | |
If useStepVisualization Then | |
colWidths = Array(3) | |
Else | |
colWidths = Array(ed * 2) | |
End If | |
If useStepVisualization Then | |
For i = 1 To ed - 1 | |
Call PushToBackOfArray(header1Titles, "") | |
Call PushToBackOfArray(header2Titles, "") | |
Call PushToBackOfArray(colWidths, 2) | |
Next i | |
End If | |
' Then populate the arrays with the rest of the header titles | |
For i = 0 To UBound(preliminaryHeader1Titles) | |
Call PushToBackOfArray(header1Titles, preliminaryHeader1Titles(i)) | |
Call PushToBackOfArray(header2Titles, preliminaryHeader2Titles(i)) | |
Call PushToBackOfArray(colWidths, preliminaryColWidths(i)) | |
Next i | |
' Set the header widths | |
For i = 0 To UBound(colWidths) | |
Set cell = Cells(rowIndex, colIndex + i) | |
cell.ColumnWidth = colWidths(i) | |
Next i | |
' Create the header, then increment the row index by 1 | |
Call GenerateHeaderRow(colIndex, rowIndex, header1Titles) | |
rowIndex = rowIndex + 1 | |
' Create the tables for every possible generator pair | |
For i = 1 To (((ed / periods)) / 2) | |
' Create the progenitor scale | |
Dim progenitorScale() As Variant | |
Dim progenitorLargeStep As Integer | |
Dim progenitorSmallStep As Integer | |
progenitorScale = Array() | |
progenitorSmallStep = i | |
progenitorLargeStep = (ed / periods) - progenitorSmallStep | |
For j = 0 To periods - 1 | |
Call PushToBackOfArray(progenitorScale, progenitorLargeStep) | |
Call PushToBackOfArray(progenitorScale, progenitorSmallStep) | |
Next j | |
' Generate the header row for the subtable, then increment the row index by 1 | |
header2Titles(0) = "Steps for Generators " & progenitorLargeStep & "\" & (ed) & " and " & progenitorSmallStep & "\" & (ed) | |
Call GenerateHeaderRow(colIndex, rowIndex, header2Titles) | |
rowIndex = rowIndex + 1 | |
' Generate the subtable for the progenitor scale | |
Call GenerateTableForProgenitorScale(progenitorScale, ed, rowIndex, colIndex, useStepVisualization) | |
Next i | |
rowIndex = rowIndex + 1 | |
' Move the active cell | |
Set cellToMoveTo = Cells(rowIndex, colIndex) | |
cellToMoveTo.Select | |
End Sub | |
' Helper subroutine; makes an entire row of header cells, even if those cells span multiple columns | |
' Header titles are passed in as an array of strings where the length of the array denotes the number | |
' of cells. For example, if an array is passed like this: ("title1", "", "", "", "title2", "", "title3"), | |
' it's interpreted as 3 header cells where title1 spans 4 cells and title2 spans 2; so the rules are: | |
' - All cells in the header row are given the horizontal alignment of center across selection. | |
' - If a cell spans n cells, it's succeeded by n-1 blank cells. | |
Sub GenerateHeaderRow(ByRef colIndex As Integer, ByRef rowIndex As Integer, ByRef headerTitles As Variant) | |
' Calculate the number of cells the header row spans | |
Dim numHeaderCols As Integer | |
numHeaderCols = UBound(headerTitles) + 1 | |
Set cell1 = Cells(rowIndex, colIndex) | |
Set cell2 = Cells(rowIndex, colIndex + numHeaderCols - 1) | |
With ActiveSheet.Range(cell1, cell2) | |
.HorizontalAlignment = xlCenterAcrossSelection | |
.Borders(xlEdgeBottom).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeTop).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeLeft).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeRight).LineStyle = XlLineStyle.xlContinuous | |
End With | |
For i = 0 To numHeaderCols - 1 | |
Set cell = Cells(rowIndex, colIndex + i) | |
cell.Value = headerTitles(i) | |
With cell | |
.HorizontalAlignment = xlCenterAcrossSelection | |
.Borders(xlEdgeBottom).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeTop).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeLeft).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeRight).LineStyle = XlLineStyle.xlContinuous | |
End With | |
Next i | |
End Sub | |
' Helper subroutine; generates a table of mosses for a given progenitor scale | |
' The progenitor scale is usually a generator pair of the form 1L 1s, though it | |
' can also be repetitions of Ls (so nL ns). This requires a rowOffset and colOffset | |
' so that the subroutine can navigate the spreadsheet, and those values are mutated | |
' by the subroutine. | |
Public Sub GenerateTableForProgenitorScale(ByRef progenitorScale() As Variant, ByRef ed As Integer, ByRef rowIndex As Integer, ByRef colIndex As Integer, ByRef useStepVisualization As Boolean) | |
' Get the large step and small step | |
' These are needed to generate the progenitor scale but are used later | |
' like they're iterators. | |
Dim largeStep As Integer | |
Dim smallStep As Integer | |
largeStep = GetLargeStep(progenitorScale) | |
smallStep = GetSmallStep(progenitorScale) | |
' Make a copy of the progenitor scale. This way, the progenitor scale doesn't have | |
' to be mutated. | |
Dim stepCount As Integer | |
stepCount = GetStepCount(progenitorScale) | |
Dim parentScale() As Variant | |
parentScale = Array() | |
For i = 0 To (stepCount - 1) | |
Call PushToBackOfArray(parentScale, progenitorScale(i)) | |
Next i | |
' Add the rows for the scales | |
Do Until largeStep = smallStep | |
If useStepVisualization Then | |
Call GenerateTableRowForParentScale(parentScale, ed, rowIndex, colIndex) | |
Else | |
Call GenerateTableRowForParentScaleWithoutVisualization(parentScale, rowIndex, colIndex) | |
End If | |
rowIndex = rowIndex + 1 | |
' Get the child scale and assign it to parent scale, | |
' then update the large step and small step | |
parentScale = GetChildScale(parentScale) | |
largeStep = GetLargeStep(parentScale) | |
smallStep = GetSmallStep(parentScale) | |
Loop | |
If useStepVisualization Then | |
Call GenerateTableRowForParentScale(parentScale, ed, rowIndex, colIndex) | |
Else | |
Call GenerateTableRowForParentScaleWithoutVisualization(parentScale, rowIndex, colIndex) | |
End If | |
rowIndex = rowIndex + 1 | |
' Move the active cell | |
Set cellToMoveTo = Cells(rowIndex, colIndex) | |
cellToMoveTo.Select | |
End Sub | |
' Helper subroutine for GenerateTableForProgenitorScale | |
' This function mutates rowOffset and colOffset | |
Public Sub GenerateTableRowForParentScale(ByRef parentScale() As Variant, ByRef ed As Integer, ByRef rowIndex As Integer, ByRef colIndex As Integer) | |
' Center across selection across all ed cells, and add borders | |
Set cell1 = Cells(rowIndex, colIndex) | |
Set cell2 = Cells(rowIndex, colIndex + ed - 1) | |
With ActiveSheet.Range(cell1, cell2) | |
.HorizontalAlignment = xlCenterAcrossSelection | |
.Borders(xlEdgeBottom).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeTop).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeLeft).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeRight).LineStyle = XlLineStyle.xlContinuous | |
End With | |
' Get the large and small step sizes and step count | |
Dim largeStepSize As Integer | |
Dim smallStepSize As Integer | |
Dim stepCount As Integer | |
largeStepSize = GetLargeStep(parentScale) | |
smallStepSize = GetSmallStep(parentScale) | |
stepCount = GetStepCount(parentScale) | |
' Iterate through the parent scale while also iterating through the | |
' cells in the row. For each iteration of the parent scale, whatever | |
' the step size is the number of times the column iterator should iterate. | |
' At each iteration of the parent scale, place in the corresponding | |
' cell the step size. The next cell is stepSize - 1 cells away and | |
' contains the next element in the parent scale. | |
' As an example, consider the parent scale of (3, 2, 2, 2). | |
' The step diagram spans 9 columns, but the parent scale has 3 elements. | |
' On the first iteration, write a 3. | |
' On the second iteration, write a 2 3 cells away from the last cell, | |
' and so on. This operation will need a few iterators. | |
Dim colOffset As Integer | |
colOffset = 0 | |
For i = 0 To stepCount - 1 | |
Dim stepSize As Integer | |
stepSize = parentScale(i) | |
Set cell1 = Cells(rowIndex, colIndex + colOffset) | |
Set cell2 = Cells(rowIndex, colIndex + colOffset + stepSize - 1) | |
cell1.Value = stepSize | |
With ActiveSheet.Range(cell1, cell2) | |
.Borders(xlEdgeBottom).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeTop).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeLeft).LineStyle = XlLineStyle.xlContinuous | |
.Borders(xlEdgeRight).LineStyle = XlLineStyle.xlContinuous | |
End With | |
colOffset = colOffset + stepSize | |
Next i | |
' Populate the remaining rows | |
' The first column after the step pattern columns is the mos (formatted as xL ys) | |
' The second column is the step ratio, and it needs to be entered in a way that | |
' doesn't get misinterpreted as a time (hh:mm) | |
' The third column is the tamnams name, which requires a lookup table | |
' The fourth column is the scale, formatted as temperament[n]. This has to be | |
' entered in manually since different edos support different temperaments. | |
' Populate the mos cell | |
Set cell1 = Cells(rowIndex, colIndex + ed) | |
'cell1.Value = "[[" & GetMosScaleAsString(parentScale) & "]]" | |
cell1.Value = GetMosScaleAsString(parentScale) | |
' Populate the step ratio cell | |
Set cell1 = Cells(rowIndex, colIndex + ed + 1) | |
cell1.Value = "=" & Chr(34) & GetStepRatioAsString(parentScale) & Chr(34) | |
' Populate the tamnams cell | |
Set cell1 = Cells(rowIndex, colIndex + ed + 2) | |
' To populate the tamnams cell, the step counts are needed | |
Dim largeStepCount As Integer | |
Dim smallStepCount As Integer | |
largeStepCount = GetLargeStepCount(parentScale) | |
smallStepCount = GetSmallStepCount(parentScale) | |
Dim tamnamsName As String | |
tamnamsName = GetTamnamsName(parentScale, 0) | |
cell1.Value = tamnamsName | |
' Move the active cell | |
Set cellToMoveTo = Cells(rowIndex, colIndex) | |
cellToMoveTo.Select | |
End Sub | |
Public Sub GenerateTableRowForParentScaleWithoutVisualization(ByRef parentScale() As Variant, ByRef rowIndex As Integer, ByRef colIndex As Integer) | |
' Without a step visualization, just populate the step row with a listing of all the steps | |
Set cell1 = Cells(rowIndex, colIndex) | |
Dim scaleCode As String | |
scaleCode = GetScaleCodeAsString(parentScale, True) | |
cell1.Value = scaleCode | |
' Populate the remaining rows | |
' The first column after the step pattern columns is the mos (formatted as xL ys) | |
' The second column is the step ratio, and it needs to be entered in a way that | |
' doesn't get misinterpreted as a time (hh:mm) | |
' The third column is the tamnams name, which requires a lookup table | |
' The fourth column is the scale, formatted as temperament[n]. This has to be | |
' entered in manually since different edos support different temperaments. | |
' Populate the mos cell | |
Set cell1 = Cells(rowIndex, colIndex + 1) | |
'cell1.Value = "[[" & GetMosScaleAsString(parentScale) & "]]" | |
cell1.Value = GetMosScaleAsString(parentScale) | |
' Populate the step ratio cell | |
Set cell1 = Cells(rowIndex, colIndex + 2) | |
cell1.Value = "=" & Chr(34) & GetStepRatioAsString(parentScale) & Chr(34) | |
' Populate the tamnams cell | |
Set cell1 = Cells(rowIndex, colIndex + 3) | |
' To populate the tamnams cell, the step counts are needed | |
Dim largeStepCount As Integer | |
Dim smallStepCount As Integer | |
largeStepCount = GetLargeStepCount(parentScale) | |
smallStepCount = GetSmallStepCount(parentScale) | |
Dim tamnamsName As String | |
tamnamsName = GetTamnamsName(parentScale, 0) | |
cell1.Value = tamnamsName | |
' Move the active cell | |
Set cellToMoveTo = Cells(rowIndex, colIndex) | |
cellToMoveTo.Select | |
End Sub | |
' https://bettersolutions.com/vba/arrays/passing-arrays.htm | |
' If passing ByRef, the arguments can be changed by the subroutine/function | |
' This function takes in a parent scale (represented as an array of two numbers) | |
' and produces its child scale, which has more numbers than its parent. | |
' Here's an example: the scale represented by 5 5 5 3 5 5 3 | |
' - There are 7 notes and the child scale (2 3 2 3 2 3 3 2 3 2 3 3) has 12 notes. | |
' - The rules are as follows: for a scale with x large steps and y small steps | |
' in the parent scale, its child scale will have 2x+y steps; this is because | |
' the rules of a mos are such that the large step breaks down into the child | |
' scale's large and small step and the parent's small step becomes either the | |
' child's large step or small step. | |
' There are a few considerations: | |
' - This needs to be written differently than with C++ data structures; for one, | |
' I can't push_back() in VBA, so instead I have to ReDim it each time I wanna | |
' add a new element, but I can use ReDim Preserve | |
' - With the rules of mosses, there is an order in which the large step breaks down | |
' into the next large and small steps: | |
' - L and s are the parent steps and L' and s' are the child steps | |
' - If L - s > s (or if L > 2s), then L' = L-s and s' = s | |
' - If L - s < s (or if L < 2s), then L' = s and s' = L-s | |
' - These step orderings are inherently circular, but when represented as an array, | |
' they'll either be in the scale's brightest mode or darkest mode. For our purposes, | |
' a scale is in its brightest mode when it starts with L and ends with s, and in | |
' its darkest mode when it starts with s and ends with L. | |
' - If the parent scale is in its brightest mode and L > 2s, then every instance of L | |
' in the parent scale translates to L' and s', and every s translates to s'. | |
' - If the parent scale is in its darkest mode and L > 2s, then every instance of L | |
' in the parent scale translates to s' and L', and every s translates to s'. | |
' - If the parent scale is in its brightest mode and L < 2s, then every instance of L | |
' in the parent scale translates to s' and L', and every s translates to L'. | |
' - If the parent scale is in its darkest mode and L < 2s, then every instance of L | |
' in the parent scale translates to L' and s' and every s translates to L'. | |
' - The aforementiond rules above can be simplified as such: | |
' - If Not ((parent scale in brightest mode) Xor (L > 2s)) then L translates to L' and s'; | |
' otherwise, it translates to s' and L'. | |
' - If L > 2s then s translates to s'; othewise, it translates to L'. | |
' - Without getting too technical about the rules of a mos, as long as the scale started | |
' out as either Ls or sL, these rules will apply without issue, and any other scale | |
' produced this way will always be in its brightest or darkest mode. (For multi-period | |
' scales, it can start as a repetition of Ls or sL but not both.) | |
' - Technical details: | |
' - Just because a scale starts with L and ends with s doesn't necessarily mean it's | |
' the scale's brightest mode. Diatonic ionian and lydian both start with L and end | |
' with s, but lydian (LLLsLLs) is the brightest mode, not ionian (LLsLLLs). | |
' - There can be cases where a scale can start and end with the same step; this is the | |
' case with diatonic dorian (LsLLLsL). | |
' - This is why it's important for the progenitor scale (the parent scale corresponding | |
' to 1L 1s) to start out as either Ls or sL; there are only two possible modes for | |
' the scale 1L 1s and they're both the scale's brightest and darkest modes; given the | |
' rules for how the child scale is generated, and as long as those rules are upheld, | |
' the child scale will always be in either the brightest or darkest mode. | |
Function GetChildScale(ByRef parentScale() As Variant) As Variant | |
' Get the note sizes | |
Dim largeStep As Integer | |
Dim smallStep As Integer | |
largeStep = GetLargeStep(parentScale) | |
smallStep = GetSmallStep(parentScale) | |
' Get the note count | |
' This is needed to help get the index of the last step in the scale | |
Dim stepCount As Integer | |
stepCount = GetStepCount(parentScale) | |
' Determine whether the scale is in its brightest or darkest mode. | |
' If the first step is the large step and the last the small step, it's in the brightest mode. | |
' If the first step is the small step and the last the large step, it's in the darkest mode. | |
Dim parentScaleInBrightestMode As Boolean | |
If parentScale(0) = largeStep And parentScale(stepCount - 1) = smallStep Then | |
parentScaleInBrightestMode = True | |
Else | |
parentScaleInBrightestMode = False | |
End If | |
' Determine whether the large step is larger than twice the small step. | |
' Alternatively, if the chroma (largeStep minus smallStep) is larger than | |
' the small step, this is equivalent to the large step being larger than | |
' twice the small step. | |
Dim chromaIsLargerThanSmallStep As Boolean | |
Dim chroma As Integer | |
chroma = largeStep - smallStep | |
If chroma > smallStep Then | |
chromaIsLargerThanSmallStep = True | |
Else | |
chromaIsLargerThanSmallStep = False | |
End If | |
' Calculate the child step sizes | |
' If the chroma is larger than the small step, then childLargeStep is the chroma | |
' and childSmallStep is the parent scale's small step | |
' If the chroma is smaller than the small step, then childLargeStep is the | |
' parent's small step and childSmallStep is the chroma | |
Dim childLargeStep As Integer | |
Dim childSmallStep As Integer | |
If chromaIsLargerThanSmallStep Then | |
childLargeStep = chroma | |
childSmallStep = smallStep | |
Else | |
childLargeStep = smallStep | |
childSmallStep = chroma | |
End If | |
' Create the child scale given the rules described | |
' Since there's no operation equivalent to push_back in VBA, the array representing | |
' the child scale is initialized to the number of notes it will have. | |
Dim childScale() As Variant | |
childScale = Array() | |
For i = 0 To stepCount - 1 | |
If parentScale(i) = largeStep Then | |
If Not (parentScaleInBrightestMode Xor chromaIsLargerThanSmallStep) Then | |
Call PushToBackOfArray(childScale, childLargeStep) | |
Call PushToBackOfArray(childScale, childSmallStep) | |
Else | |
Call PushToBackOfArray(childScale, childSmallStep) | |
Call PushToBackOfArray(childScale, childLargeStep) | |
End If | |
ElseIf parentScale(i) = smallStep Then | |
If chromaIsLargerThanSmallStep Then | |
Call PushToBackOfArray(childScale, childSmallStep) | |
Else | |
Call PushToBackOfArray(childScale, childLargeStep) | |
End If | |
End If | |
Next i | |
GetChildScale = childScale | |
End Function | |
' Get array length | |
' Array length is upper bound minus lower bound plus 1 | |
' https://www.automateexcel.com/vba/array-length-size/ | |
Public Function GetStepCount(ByRef parentScale() As Variant) As Integer | |
If IsEmpty(parentScale) Then | |
GetStepCount = 0 | |
Else | |
GetStepCount = UBound(parentScale) - LBound(parentScale) + 1 | |
End If | |
End Function | |
' Returns the largest value in an array | |
Public Function GetLargeStep(ByRef parentScale() As Variant) As Integer | |
GetLargeStep = WorksheetFunction.Max(parentScale) | |
End Function | |
' Returns the smallest value in an array | |
Public Function GetSmallStep(ByRef parentScale() As Variant) As Integer | |
GetSmallStep = WorksheetFunction.Min(parentScale) | |
End Function | |
' Returns the number of large steps in the scale | |
Public Function GetLargeStepCount(ByRef parentScale() As Variant) As Integer | |
Dim stepCount As Integer | |
Dim largeStep As Integer | |
Dim largeStepCount As Integer | |
stepCount = GetStepCount(parentScale) | |
largeStep = GetLargeStep(parentScale) | |
largeStepCount = 0 | |
For i = 0 To stepCount - 1 | |
If parentScale(i) = largeStep Then | |
largeStepCount = largeStepCount + 1 | |
End If | |
Next i | |
GetLargeStepCount = largeStepCount | |
End Function | |
' Returns the number of small steps in the scale | |
Public Function GetSmallStepCount(ByRef parentScale() As Variant) As Integer | |
Dim stepCount As Integer | |
Dim smallStep As Integer | |
Dim smallStepCount As Integer | |
stepCount = GetStepCount(parentScale) | |
smallStep = GetSmallStep(parentScale) | |
smallStepCount = 0 | |
For i = 0 To stepCount - 1 | |
If parentScale(i) = smallStep Then | |
smallStepCount = smallStepCount + 1 | |
End If | |
Next i | |
GetSmallStepCount = smallStepCount | |
End Function | |
' Utility subroutine; push_back() function | |
' This is technically slower than C++'s push_back() for vectors; vectors have a | |
' hidden array that's resized exponentially whenever it runs out of room, but this | |
' resizes by 1 every time this is called. | |
Public Sub PushToBackOfArray(ByRef a() As Variant, ByRef p As Variant) | |
' arraySize is used as the index of the new upper bound for the array | |
Dim arraySize As Integer | |
If IsEmpty(a) Then | |
arraySize = 0 | |
Else | |
arraySize = UBound(a) - LBound(a) + 1 | |
End If | |
ReDim Preserve a(arraySize) | |
a(arraySize) = p | |
End Sub | |
' For debug purposes; print scale information | |
Public Sub PrintScaleInformation(ByRef parentScale() As Variant) | |
MsgBox ("Scale: " & GetMosScaleAsString(parentScale) & vbNewLine & "Step count: " & GetStepCount(parentScale) & vbNewLine & "Step ratio: " & GetStepRatioAsString(parentScale) & vbNewLine & "Scale code: " & GetScaleCodeAsString(parentScale)) | |
End Sub | |
' For debug purposes; get scale (formatted as xL ys) | |
Public Function GetMosScaleAsString(ByRef parentScale() As Variant) As String | |
Dim largeStepCount As Integer | |
Dim smallStepCount As Integer | |
Dim largeStep As Integer | |
Dim smallStep As Integer | |
largeStepCount = GetLargeStepCount(parentScale) | |
smallStepCount = GetSmallStepCount(parentScale) | |
largeStep = GetLargeStep(parentScale) | |
smallStep = GetSmallStep(parentScale) | |
If largeStep = smallStep Then | |
GetMosScaleAsString = GetStepCount(parentScale) & "edo" | |
Else | |
GetMosScaleAsString = largeStepCount & "L " & smallStepCount & "s" | |
End If | |
End Function | |
' For debug purposes; get step ratio | |
Public Function GetStepRatioAsString(ByRef parentScale() As Variant) As String | |
Dim largeStep As Integer | |
Dim smallStep As Integer | |
largeStep = GetLargeStep(parentScale) | |
smallStep = GetSmallStep(parentScale) | |
If largeStep = smallStep Then | |
GetStepRatioAsString = largeStep | |
Else | |
GetStepRatioAsString = largeStep & ":" & smallStep | |
End If | |
End Function | |
' For debug purposes; get scale code, either as a string of L's and s's (false) or numerically (true) | |
Public Function GetScaleCodeAsString(ByRef parentScale() As Variant, ByRef useStepSizes As Boolean) As String | |
Dim stepCount As Integer | |
Dim largeStep As Integer | |
Dim smallStep As Integer | |
largeStep = GetLargeStep(parentScale) | |
smallStep = GetSmallStep(parentScale) | |
stepCount = GetStepCount(parentScale) | |
Dim scaleCode As String | |
scaleCode = "" | |
' The scale code can either be a string of L's and s's, or be a string of two numbers separated by spaces | |
If useStepSizes Then | |
For i = 0 To stepCount - 1 | |
scaleCode = scaleCode + CStr(parentScale(i)) | |
If Not (stepCount - 1) Then | |
scaleCode = scaleCode + " " | |
End If | |
Next i | |
Else | |
For i = 0 To stepCount - 1 | |
If largeStep = smallStep Then | |
scaleCode = scaleCode & "u" | |
ElseIf parentScale(i) = smallStep Then | |
scaleCode = scaleCode & "s" | |
ElseIf parentScale(i) = largeStep Then | |
scaleCode = scaleCode & "L" | |
End If | |
Next i | |
End If | |
GetScaleCodeAsString = scaleCode | |
End Function | |
' This is a lookup table for Tamnams names | |
' This also includes a few extended names, mainly "protic/prototonic" and "deuteric/deuterotonic" | |
' but those names can be configured | |
' - 2: Use all extended names | |
' - 1: tetric, kleistonic, and hexawood only | |
' - 0 and any other value: tamnams names only | |
Public Function GetTamnamsName(ByRef parentScale() As Variant, ByRef useExtendedNames As Integer) As String | |
' Rows of the lookup table | |
Dim lookupTable02() As Variant | |
Dim lookupTable03() As Variant | |
Dim lookupTable04() As Variant | |
Dim lookupTable05() As Variant | |
Dim lookupTable06() As Variant | |
Dim lookupTable07() As Variant | |
Dim lookupTable08() As Variant | |
Dim lookupTable09() As Variant | |
Dim lookupTable10() As Variant | |
Dim lookupTable11() As Variant | |
Dim lookupTable12() As Variant | |
' Extended tamnams names are based on existing names wherever possible | |
' - All 1L ns scales are named based on the nL 1s scale name with the anti- prefix added | |
' - prototonic/protic is used in referenc that 1L 1s is the progenitor scale for all possible (single-period) scales; can also be called monowood | |
' - In following similar logic, protic only has two possible children, named deuterotonic/deuteric and antideuterotonic/antideuteric | |
' - tetric is used to refer to 3L 1s the same way 2L 3s refers to pentic; antipentic follows as a 1L ns scale | |
' - diwood (2L 2s) is named in reference to the n-wood scales (nL ns) | |
' - kleistonic (4L 7s) appears on the wiki but not on the tamnams page | |
' - Many 12-note mosses are named as extensions of either 2-note (hexa-), 4-note (tri-), 6-note (di-), or 8-note (sesqui-) scales | |
If useExtendedNames = 2 Then | |
lookupTable02 = Array("protic; monowood") | |
lookupTable03 = Array("antideuteric", "deuteric") | |
lookupTable04 = Array("antitetric", "diwood", "tetric") | |
lookupTable05 = Array("antimanic", "pentic", "antipentic", "manic") | |
lookupTable06 = Array("antimachinoid", "antilemon", "triwood", "lemon", "machinoid") | |
lookupTable07 = Array("anti-archeotonic", "antidiatonic", "mosh", "smitonic", "diatonic", "archeotonic") | |
lookupTable08 = Array("antipine", "antiechidnoid", "sensoid", "tetrawood; diminished", "oneirotonic", "echidnoid", "pine") | |
lookupTable09 = Array("antisubneutralic", "joanatonic", "tcherepnin", "orwelloid", "semiquartal", "hyrulic", "superdiatonic", "subneutralic") | |
lookupTable10 = Array("antisinatonic", "antidimanic", "sephiroid", "antidipentic", "pentawood", "dipentic", "dicotonic", "dimanic", "sinatonic") | |
lookupTable11 = Array("", "", "", "kleistonic", "", "", "", "", "", "") | |
lookupTable12 = Array("", "antidimachinoid", "antitritetric, antisesqui-echidnoid", "antidilemon", "p-chromatic", "hexawood", "m-chromatic", "dilemon", "tritetric, sesqui-echidnoid", "dimachinoid", "") | |
ElseIf useExtendedNames = 1 Then | |
lookupTable02 = Array("") | |
lookupTable03 = Array("", "") | |
lookupTable04 = Array("", "diwood", "tetric") | |
lookupTable05 = Array("", "pentic", "antipentic", "manic") | |
lookupTable06 = Array("", "antilemon", "triwood", "lemon", "machinoid") | |
lookupTable07 = Array("", "antidiatonic", "mosh", "smitonic", "diatonic", "archeotonic") | |
lookupTable08 = Array("", "antiechidnoid", "sensoid", "tetrawood; diminished", "oneirotonic", "echidnoid", "pine") | |
lookupTable09 = Array("", "joanatonic", "tcherepnin", "orwelloid", "semiquartal", "hyrulic", "superdiatonic", "subneutralic") | |
lookupTable10 = Array("", "antidimanic", "sephiroid", "antidipentic", "pentawood", "dipentic", "dicotonic", "dimanic", "sinatonic") | |
lookupTable11 = Array("", "", "", "kleistonic", "", "", "", "", "", "") | |
lookupTable12 = Array("", "", "", "", "p-chromatic", "hexawood", "m-chromatic", "", "", "", "") | |
Else | |
lookupTable02 = Array("") | |
lookupTable03 = Array("", "") | |
lookupTable04 = Array("", "", "") | |
lookupTable05 = Array("", "pentic", "antipentic", "manic") | |
lookupTable06 = Array("", "antilemon", "triwood", "lemon", "machinoid") | |
lookupTable07 = Array("", "antidiatonic", "mosh", "smitonic", "diatonic", "archeotonic") | |
lookupTable08 = Array("", "antiechidnoid", "sensoid", "tetrawood; diminished", "oneirotonic", "echidnoid", "pine") | |
lookupTable09 = Array("", "joanatonic", "tcherepnin", "orwelloid", "semiquartal", "hyrulic", "superdiatonic", "subneutralic") | |
lookupTable10 = Array("", "antidimanic", "sephiroid", "antidipentic", "pentawood", "dipentic", "dicotonic", "dimanic", "sinatonic") | |
lookupTable11 = Array("", "", "", "", "", "", "", "", "", "") | |
lookupTable12 = Array("", "", "", "", "p-chromatic", "", "m-chromatic", "", "", "", "") | |
End If | |
' Calculate the large step count and note count; that's all is needed | |
Dim largeStepCount As Integer | |
Dim noteCount As Integer | |
largeStepCount = GetLargeStepCount(parentScale) | |
noteCount = GetStepCount(parentScale) | |
' If the large step and small steps are the same sizes, the scale doesn't | |
' get a tamnams name, since the steps are the same size (and technically, | |
' there'd be no distinction between xL ys and xL xs) | |
Dim largeStep As Integer | |
Dim smallStep As Integer | |
largeStep = GetLargeStep(parentScale) | |
smallStep = GetSmallStep(parentScale) | |
' Use the large step as an indexer for the row of the lookup table | |
Dim rowIndexer As Integer | |
rowIndexer = largeStepCount - 1 | |
If (largeStep = smallStep) Then | |
GetTamnamsName = "" | |
ElseIf noteCount = 2 Then | |
GetTamnamsName = lookupTable02(rowIndexer) | |
ElseIf noteCount = 3 Then | |
GetTamnamsName = lookupTable03(rowIndexer) | |
ElseIf noteCount = 4 Then | |
GetTamnamsName = lookupTable04(rowIndexer) | |
ElseIf noteCount = 5 Then | |
GetTamnamsName = lookupTable05(rowIndexer) | |
ElseIf noteCount = 6 Then | |
GetTamnamsName = lookupTable06(rowIndexer) | |
ElseIf noteCount = 7 Then | |
GetTamnamsName = lookupTable07(rowIndexer) | |
ElseIf noteCount = 8 Then | |
GetTamnamsName = lookupTable08(rowIndexer) | |
ElseIf noteCount = 9 Then | |
GetTamnamsName = lookupTable09(rowIndexer) | |
ElseIf noteCount = 10 Then | |
GetTamnamsName = lookupTable10(rowIndexer) | |
ElseIf noteCount = 11 Then | |
GetTamnamsName = lookupTable11(rowIndexer) | |
ElseIf noteCount = 12 Then | |
GetTamnamsName = lookupTable12(rowIndexer) | |
Else | |
GetTamnamsName = "" | |
End If | |
End Function |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment