<jupyter_start><jupyter_text>Scale generator for SunVoxThis is a series of note translators for SunVox that lock your notesinto a specific scale.It requires SunVox 1.9.3 or higher to use, as it makes use of MultiSynth event output. How to use themLoad the sunsynth for the scale you'd like to use.Send notes to it, via a tracker, MIDI, or from another module.Connect the scale's output to the input of a synth.The notes sent to the synth will be locked into the scale you loaded. How it works[*musthe*](https://github.com/gciruelos/musthe) provides a basic music theory engine. We maintain fork in the same directory as this notebook.[*Radiant Voices*](https://radiant-voices.readthedocs.io/) lets us construct and write SunVox compatible project files.The code below goes through all notes and scales available in *musthe*, and creates the structures needed to lock notes into those scales.<jupyter_code>import musthe as m
import rv.api as rv
def scale_project(root_note, scale_name):
    name = '{} {}'.format(root_note, scale_name).replace('_', ' ')
    filename = '{}.sunsynth'.format(name)
    
    scale = m.scale(m.Note(root_note), scale_name=scale_name)
    scale_ids = sorted({n.note_id for n in scale})
    
    proj = rv.Project()
    proj.name = name
    in_mod = proj.new_module(rv.m.MultiSynth, name='I {}'.format(name))
    in_mod.color = (0, 255, 0)
    out_mod = proj.output
    gate_mods = {}
    
    metamod = rv.m.MetaModule(project=proj)
    metamod.input_module = in_mod.index
    metamod.name = proj.name
    
    synth = rv.Synth(metamod)

    def gate_mod(offset):
        if offset not in gate_mods:
            gate = gate_mods[offset] = proj.new_module(
                rv.m.MultiSynth, 
                name='G{} {}'.format(offset, name),
            )
            gate.color = (64, 64, 64)
            if offset > 0:
                transpose = proj.new_module(
                    rv.m.MultiSynth,
                    name='T{} {}'.format(offset, name),
                    transpose=-offset,
                )
                transpose.color = (64, 64, 64)
                in_mod >> gate >> transpose >> out_mod
            else:
                in_mod >> gate >> out_mod
            for x in range(128):
                gate.nv_curve.values[x] = 0
        return gate_mods[offset]

    # Start one octave below to ensure offset is initialized
    # once we get to the note range we care about.
    offset = 0
    for note in range(-12, 128):
        if note % 12 in scale_ids:
            offset = 0
        else:
            offset += 1
        gate = gate_mod(offset)
        if note >= 0:
            gate.nv_curve.values[note] = 255

    proj.in_mod = in_mod
            
    proj.layout(prog='dot')            
    return synth, filename
all_scales = rv.Project()
all_scales_mod = rv.m.MetaModule(project=all_scales)
all_scales_synth = rv.Synth(module=all_scales_mod)

all_scales_transposed = rv.Project()
all_scales_transposed_mod = rv.m.MetaModule(project=all_scales_transposed)
all_scales_transposed_synth = rv.Synth(module=all_scales_transposed_mod)

x_scale = 64
y_scale = 64

for x, root_note in enumerate([
    'C', 
    'C#', 
    'Db', 
    'D', 
    'D#', 
    'Eb', 
    'E', 
    'F', 
    'F#', 
    'Gb', 
    'G', 
    'G#', 
    'Ab', 
    'A', 
    'A#', 
    'Bb', 
    'B',
], 1):
    root_note_id = m.Note(root_note).note_id
    for y, scale_name in enumerate([
        'major',
        'natural_minor',
        'harmonic_minor',
        'melodic_minor',
        'dorian',
        'locrian',
        'lydian',
        'mixolydian',
        'phrygian',
        'major_pentatonic',
        'minor_pentatonic',
    ], 1):
        try:
            synth, filename = scale_project(root_note, scale_name)
            filename = filename.replace('#', 's')
            with open(filename, 'wb') as f:
                synth.write_to(f)
            transposed = None
            if root_note_id:
                filename = 'Transposed to C - {}'.format(filename)
                transposed = synth.clone()
                transposed.module.name = 'T {}'.format(synth.module.name)
                in_mod = transposed.module.project.modules[synth.module.project.in_mod.index]
                in_mod.transpose = -root_note_id
                with open(filename, 'wb') as f:
                    transposed.write_to(f)
            def wire(mod, project):
                project += mod
                project.output << mod
                mod.x = project.output.x + (x * x_scale)
                mod.y = project.output.y + (y * y_scale)
            wire(synth.module, all_scales)
            if transposed:
                wire(transposed.module, all_scales_transposed)
        except Exception as e:
            print('*** :-(', root_note, scale_name, e)
        else:
            print('    :-)', synth.module.name)

with open('All Scales.sunsynth', 'wb') as f:
    all_scales_synth.write_to(f)
print('    :-) All Scales')
    
with open('All Scales Transposed to C.sunsynth', 'wb') as f:
    all_scales_transposed_synth.write_to(f)
print('    :-) All Scales Transposed to C')<jupyter_output>:-) C major
    :-) C natural minor
    :-) C harmonic minor
    :-) C melodic minor
    :-) C dorian
    :-) C locrian
    :-) C lydian
    :-) C mixolydian
    :-) C phrygian
    :-) C major pentatonic
    :-) C minor pentatonic
    :-) C# major
    :-) C# natural minor
    :-) C# harmonic minor
    :-) C# melodic minor
    :-) C# dorian
    :-) C# locrian
    :-) C# lydian
    :-) C# mixolydian
    :-) C# phrygian
    :-) C# major pentatonic
    :-) C# minor pentatonic
    :-) Db major
*** :-( Db natural_minor Could not parse the note: C#######################4
    :-) Db harmonic minor
    :-) Db melodic minor
*** :-( Db dorian Could not parse the note: C#######################4
*** :-( Db locrian Could not parse the note: C#######################4
    :-) Db lydian
*** :-( Db mixolydian Could not parse the note: C#######################4
*** :-( Db phrygian Could not parse the note: C#######################4
    :-) Db major pentatonic
*** :-( Db minor_pentatonic Could not par[...]
{'fraction_non_alphanumeric': 0.13550488599348534, 'fraction_numerical': 0.008143322475570033, 'mean_word_length': 2.1427840327533265, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 16, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '78564', 'license': 'Unlicense', 'n_tokens_mistral': 2092, 'n_tokens_neox': 1938, 'n_words': 541, 'path': 'scales/scale generator.ipynb', 'repo_name': 'metrasynth/gallery', 'size': '6140'}