{"id":13418767,"url":"https://github.com/celesteh/TuningLib","last_synced_at":"2025-03-15T04:30:23.453Z","repository":{"id":25093364,"uuid":"28514337","full_name":"celesteh/TuningLib","owner":"celesteh","description":"A superCollider Quark to handle several tasks related to tuning","archived":false,"fork":false,"pushed_at":"2024-12-06T17:32:59.000Z","size":76,"stargazers_count":16,"open_issues_count":2,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-12-06T19:11:29.540Z","etag":null,"topics":["quark","scale","supercollider","tuning"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/celesteh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-12-26T15:37:21.000Z","updated_at":"2024-12-06T17:28:29.000Z","dependencies_parsed_at":"2024-10-26T22:01:38.458Z","dependency_job_id":null,"html_url":"https://github.com/celesteh/TuningLib","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celesteh%2FTuningLib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celesteh%2FTuningLib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celesteh%2FTuningLib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celesteh%2FTuningLib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/celesteh","download_url":"https://codeload.github.com/celesteh/TuningLib/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243685503,"owners_count":20330980,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["quark","scale","supercollider","tuning"],"created_at":"2024-07-30T22:01:06.803Z","updated_at":"2025-03-15T04:30:23.440Z","avatar_url":"https://github.com/celesteh.png","language":"HTML","funding_links":[],"categories":["HTML"],"sub_categories":[],"readme":"TuningLib\n=========\n\nA SuperCollider Quark which helps with Tuning \n\nHere is a talk I gave about the quark in 2013:\n\n## Picking Musical Tones\n\nOne of the great problems in electronic music is picking pitches and tunings.\n\nThe TuningLib quark helps manage this process.\n\nFirst, there is some Scale stuff already in SuperColider.\n\nHow to use a scale in a Pbind:\n\n```supercollider\n(\ns.waitForBoot({\n\t    a = Scale.ionian;\n\n\t    p = Pbind(\n\t\t        \\degree, Pseq([0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0, \\rest], 2),\n\t\t        \\scale, a,\n\t\t        \\dur, 0.25\n\t    );\n\n\t    q = p.play;\n})\n)\n```\n\n## Key\n\nKey tracks key changes and modulations, so you can keep modulating or back out of modulations:\n\n```supercollider\nk = Key(Scale.choose);\nk.scale.degrees;\nk.scale.cents;\nk.change(4); // modulate to the 5th scale degree (we start counting with 0)\nk.scale.degrees;\nk.scale.cents;\nk.change; // go back\n\nk.scale.degrees;\n```\n\nThis will keep up through as many layers of modulations as you want.  These modulations keep track of transpositions in any tuning system. This means, that if you modulate to 3/2 in Just Intonation, it will do all the necessary multiplcations for you. This is compatible with any tuning system.\n\nIt also does rounding:\n\n```supercollider\nquantizeFreq (freq, base, round , gravity )\n```\n\nSnaps the feq value in Hz to the nearest Hz value in the current key\n\ngravity changes the level of attraction ot the in tune frequency.\n\n```supercollider\nk.quantizeFreq(660, 440, \\down, 0.5) // half way in tune\n```\n\nBy changing gravity over time, you can have pitches tend towards being in or out of tune.\n\n## Scala\n\nThere is a huge library of pre-cooked tunings for the scala program. ( at http://www.huygens-fokker.org/scala/scl_format.html\n) This class opens those files.\n\n```supercollider\na = Scala(\"slendro.scl\");\nb = a.scale;\n```\n\n## Diamond (formerly Lattice)\n\nThis is a partchian tuning diamond \n\n```supercollider\nd = Diamond([ 2, 5, 3, 7, 9])\n```\n\nThe array is numbers to use in generated tuning ratios, so this gives:\n\n* 1/1 5/4 3/2 7/4 9/8   for otonality\n* 1/1 8/5 4/3 8/7 16/9  for utonality\n\n* otonality is overtones – the numbers you give are in the numerator\n* utonality is undertones – the numbers are in denominator\n\nall of the other numbers are powers of 2.  You could change that with an optional second argument to any other number, such as 3:\n\n```supercollider\nd = Diamond([ 2, 3, 5, 7, 11], 3)\n```\n\nDiamonds also generate a table:\n\n|      |     |      |     |     |\n|------|-----|------|-----|-----|\n| 1/1  |5/4  | 3/2  |7/4  | 9/8 |\n| 8/5  |1/1  | 6/5  |7/5  | 9/5 |\n| 4/3  |5/3  | 1/1  |7/6  | 3/2 |\n| 8/7  |10/7 | 12/7 |1/1  | 9/7 |\n| 16/9 |10/9 | 4/3  |14/9 | 1/1 |\n\nIt is posisble to walk around this table to make nice triads that are harmonically related:\n\n```supercollider\n(\ns.waitForBoot({\n\n\tvar dia, orientation, startx, starty, baseFreq;\n\n\tSynthDef(\"sine\", {arg out = 0, dur = 5, freq, amp=0.2, pan = 0;\n\t\tvar env, osc;\n\t\tenv = EnvGen.kr(Env.sine(dur, amp), doneAction: 2);\n\t\tosc = SinOsc.ar(freq, 0, env);\n\t\tOut.ar(out, osc * amp);\n\t}).add;\n\n\ts.sync;\n\n\n\tdia = Diamond.new;\n\torientation = true;\n\tstartx = 0;\n\tstarty = 0;\n\tbaseFreq = 440;\n\n\tPbind(\n\t\t\\instrument, \\sine,\n\t\t\\amp, 0.3,\n\t\t\\freq, Pfunc({\n\t\t\tvar starts, result;\n\t\t\t  orientation = orientation.not;\n\t\t\t  starts = dia.d2Pivot(startx, starty, orientation);\n\t\t\t  startx = starts.first;\n\t\t\t  starty = starts.last;\n\t\t\tresult = dia.makeIntervals(startx, starty, orientation);\n\t\t\t(result * baseFreq)\n\t\t})\n\t).play\n})\n)\n```\n\n(Somewhat embarassingly, I got confused between 2 and 3 dimensions in a previous version. This is now fixed.)\n\n## DissonanceCurve\n\nThis is not the only quark that does dissonance curves in SuperCollider.\n\nDissonance curves are used to compute tunings based on timbre, which is to say the spectrum.\n\n```supercollider\nd = DissonanceCurve([440], [1])\nd.plot\n```\n\nThe high part of the graph is highly dissonant and the low part is not dissonant.  This is for just one pitch, but with additional pitches, the graph changes:\n\n```supercollider\nd = DissonanceCurve([335, 440], [0.7, 0.3])\nd.plot\n```\n\nThe combination of pitches produces a more complex graph with minima. Those minima are good scale steps.\n\nThis class is currently optomised for FM, but subsequent versions will calculate spectra for Ring Modulation, AM Modulation, Phase Modulation and combinations of all of those things.\n\n```supercollider\n(\n\ns.waitForBoot({\n\n\tvar carrier, modulator, depth, curve, scale, degrees;\n\n\tSynthDef(\"fm\", {arg out, amp, carrier, modulator, depth, dur, midinote = 0;\n\t\tvar sin, ratio, env;\n\n\t\tratio = midinote.midiratio;\n\t\tcarrier = carrier * ratio;\n\t\tmodulator = modulator * ratio;\n\t\tdepth = depth * ratio;\n\n\t\tsin = SinOsc.ar(SinOsc.ar(modulator, 0, depth, carrier));\n\t\tenv = EnvGen.kr(Env.perc(releaseTime: dur)) * amp;\n\t\tOut.ar(out, (sin * env).dup);\n\t}).add;\n\n\ts.sync;\n\n\tcarrier = 440;\n\tmodulator = 600;\n\tdepth = 100;\n\tcurve = DissonanceCurve.fm(carrier, modulator, depth, 1200);\n\tscale = curve.scale;\n\n\n\tdegrees = (0..scale.size); // make an array of all the scale degrees\n\n\n// We don't know how many pitches per octave  will be until after the\n// DissonanceCurve is calculated.  However, degrees outside of the range\n// will be mapped accordingly.\n\n\n\tPbind(\n\n\t\t\\instrument,\t\\fm,\n\t\t\\octave,\t0,\n\t\t\\scale,\tscale,\n\t\t\\degree,\tPseq([\n\t\t\tPseq(degrees, 1), // play one octave\n\t\t\tPseq([-3, 2, 0, -1, 3, 1], 1) // play other notes\n\t\t], 1),\n\n\t\t\\carrier,\tcarrier,\n\t\t\\modulator,\tmodulator,\n\t\t\\depth,\tdepth\n\t).play\n});\n)\n```\n\nThe only problem here is that this conflicts entirely with Just Intonation!\n\nFor just tunings based on spectra, we would calculate dissonance based on the ratios of the partials of the sound.  Low numbers are more in tune, high numbers are less in tune.\n\nThere's only one problem with this, which is visible if we generate a graph of just a sine tone:\n\n```supercollider\nd = DissonanceCurve([440], [1])\nd.just_curve.collect({|diss| diss.dissonance}).plot\n```\n\nThere is no pattern of minima and maxima. How do we pick tuning degrees?\n\nThere are two answers provided. \n\nThe first is to use a moving window where we pick the most consontant tuning within that window.  This defaults to 100 cents, assuming you want something with roughly normal step sizes.\n\nThen to pick scale steps, we can ask for the n most consontant tunings\n\n```supercollider\nt = d.justScale(100, 7); // pick the 7 most consonant tunings\n```\n\nThe other method is to limit the integer size in the ratios. \nFor example, if we pick a limit of 21, neither the numerator or the denominator will be larger than 21.\nFor this selection method, we also need to specify how many ratios we want in our tuning \n(and how many of those we want in our scale).\n\n```supercollider\nt = d.limitedJustScale(21, 11, 7); // pick the 11 most consonant tunings limitted by 21\n                 // and put the 7 most consonant tunings into a Scale\n  \n```  \n\nThe higher the limit, the more closely related the scale is to the timbre.\n  \nThese two methods will produce different scales. \nIf the timbre has a harmonic sound, the scales will be more closely related than if the sound is enharmonic.\n\n```supercollider\n(\nvar carrier, modulator, depth, curve, scale, degrees;\ncarrier = 440;\nmodulator = 600;\ndepth = 100;\ncurve = DissonanceCurve.fm(carrier, modulator, depth, 1200);\nscale = curve.justScale(100, 7); // pick the 7 most consonant tunings\ndegrees = (0..(scale.size - 1)); // make an array of all the scale degrees (you can't assume the size is 7)\n\nPbind(\n\t\\instrument, \\fm,\n\t\\octave, 0,\n\t\\scale, scale,\n\t\\degree, Pseq([\n\t\tPseq(degrees, 1), // play one octave\n\t\tPseq([-7, 2, 0, -5, 4, 1], 1)], 1), // play other notes\n\t\\carrier, carrier,\n\t\\modulator, modulator,\n\t\\depth, depth\n).play\n)\n```\n\n## Future plans\n\n* Add the ability to calculate more spectra - PM, RM AM, etc\n* Allow the user to specify how determine the consonance of Just ratios\n\n## Comments / Feature Requests\n\n* lattice - make n dimensional\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcelesteh%2FTuningLib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcelesteh%2FTuningLib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcelesteh%2FTuningLib/lists"}