Skip to content

Instantly share code, notes, and snippets.

@GanaramInukshuk
Last active March 29, 2022 08:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GanaramInukshuk/e18439e300dba0bb59bd0211ffbf2932 to your computer and use it in GitHub Desktop.
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.
' 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