Shallow Thoughts : : Oct
Akkana's Musings on Open Source Computing and Technology, Science, and Nature.
Sat, 22 Oct 2016
The Los Alamos Artists Studio Tour was last weekend. It was a fun and
somewhat successful day.
I was borrowing space in the studio of the fabulous scratchboard
artist Heather Ward,
because we didn't have enough White Rock artists signed up for the
tour.
Traffic was sporadic: we'd have long periods when nobody came by (I
was glad I'd brought my laptop, and managed to get some useful
development done on track management in pytopo),
punctuated by bursts where three or four groups would show up all at once.
It was fun talking to the people who came by. They all had questions
about both my metalwork and Heather's scratchboard, and we had a lot
of good conversations. Not many of them were actually buying -- I
heard the same thing afterward from most of the other artists on the
tour, so it wasn't just us. But I still sold enough that I more than
made back the cost of the tour. (I hadn't realized, prior to this,
that artists have to pay to be in shows and tours like this, so
there's a lot of incentive to sell enough at least to break even.) Of
course, I'm nowhere near covering the cost of materials and equipment.
Maybe some day ...
I figured snacks are always appreciated, so I set out my pelican snack
bowl -- one of my first art pieces -- with brownies and cookies in
it, next to the business cards.
It was funny how wrong I was in predicting what people would like.
I thought everyone would want the roadrunners and dragonflies; in
practice,
scorpions
were much more popular, along with a
sea serpent
that had been sitting on my garage shelf for a month while I tried to
figure out how to finish it. (I do like how it eventually came out, though.)
And then after selling both my scorpions on Saturday, I rushed to
make two more on Saturday night and Sunday morning, and of course no
one on Sunday had the slightest interest in scorpions. Dave, who used
to have a foot in the art world, tells me this is typical, and that
artists should never make what they think the market will like; just
go on making what you like yourself, and hope it works out.
Which, fortunately, is mostly what I do at this stage, since I'm mostly
puttering around for fun and learning.
Anyway, it was a good learning experience, though I was a little
stressed getting ready for it and I'm glad it's over.
Next up: a big spider for the front yard, before Halloween.
Tags: art, welding
[
20:17 Oct 22, 2016
More art |
permalink to this entry |
]
Tue, 11 Oct 2016
I'm happy to say that our state League of Women Voters Voter Guides
are out for the 2016 election.
My grandmother was active in the League of Women Voters most of her
life (at least after I was old enough to be aware of such things).
I didn't appreciate it at the time -- and I also didn't appreciate
that she had been born in a time when women couldn't legally vote,
and the 19th amendment, giving women the vote, was ratified just a
year before she reached voting age. No wonder she considered the League
so important!
The LWV continues to work to extend voting to people of all
genders, races, and economic groups -- especially important in these
days when the Voting Rights Act is under attack and so many groups
are being disenfranchised.
But the League is important for another reason: local LWV
chapters across the country produce detailed, non-partisan voter
guides for each major election, which are distributed free of charge
to voters. In many areas -- including here in New Mexico -- there's
no equivalent of the "Legislative Analyst" who writes the lengthy
analyses that appear on California ballots weighing the pros, cons and
financial impact of each measure. In the election two years ago,
not that long after Dave and I moved here, finding information on
the candidates and ballot measures wasn't easy, and the LWV Voter Guide
was by far the best source I saw. It's the main reason I joined the
League, though I also appreciate the public candidate forums and other
programs they put on.
LWV chapters are scrupulous about collecting information from
candidates in a fair, non-partisan way. Candidates' statements are
presented exactly as they're received, and all candidates are given
the same specifications and deadlines. A few candidates ignored us
this year and didn't send statements despite repeated emails and phone
calls, but we did what we could.
New Mexico's state-wide voter guide -- the one I was primarily
involved in preparing -- is at
New Mexico Voter Guide 2016.
It has links to guides from three of the four local LWV chapters: Los
Alamos, Santa Fe, and Central New Mexico (Albuquerque and surrounding areas).
The fourth chapter, Las Cruces, is still working on their guide and
they expect it soon.
I was surprised to see that our candidate information doesn't include
links to websites or social media. Apparently that's not part of the
question sheet they send out, and I got blank looks when I suggested
we should make sure to include that next time. The LWV does a lot of
important work but they're a little backward in some respects.
That's definitely on my checklist for next time, but for now, if
you want a candidate's website, there's always Google.
I also helped a little on
Los Alamos's
voter guide, making suggestions on how to present it on the
website (I maintain the state League website but not the Los Alamos
site), and participated in the committee that wrote the analysis and
pro and con arguments for
our contentious
charter amendment proposal to eliminate the elective office sheriff.
We learned a lot about the history of the sheriff's office here in Los Alamos,
and about state laws and insurance rules regarding sheriffs, and
I hope the important parts of what we learned are reflected in
both sides of the argument.
The Voter Guides also have a link to a Youtube recording of the first
Los Alamos
LWV candidate forum, featuring NM House candidates, DA, Probate judge
and, most important, the debate over the sheriff proposition.
The second candidate
forum, featuring US House of Representatives, County Council and
County Clerk candidates, will be this Thursday, October 13 at 7
(refreshments at 6:30). It will also be recorded thanks to a
contribution from the AAUW.
So -- busy, busy with election-related projects. But I think the work is
mostly done (except for the one remaining forum), the guides are out,
and now it's time to go through and read the guides.
And then the most important part of all: vote!
Tags: elections, voting, LWV, government
[
16:08 Oct 11, 2016
More politics |
permalink to this entry |
]
Wed, 05 Oct 2016
Reading
Stephen
Wolfram's latest discussion of teaching computational thinking
(which, though I mostly agree with it, is more an extended ad for
Wolfram Programming Lab than a discussion of what computational
thinking is and why we should teach it) I found myself musing over
ideas for future computer classes for
Los Alamos Makers.
Students, and especially kids, like to see something other than words
on a screen. Graphics and games good, or robotics when possible ...
but another fun project a novice programmer can appreciate is music.
I found myself curious what you could do with Python, since
I hadn't played much with Python sound generation libraries.
I did discover a while ago that
Python
is rather bad at playing audio files,
though I did eventually manage to write
a music
player script that works quite well.
What about generating tones and chords?
A web search revealed that this is another thing Python is bad at. I
found lots of people asking about chord generation, and a handful of
half-baked ideas that relied on long obsolete packages or external
program. But none of it actually worked, at least without requiring
Windows or relying on larger packages like fluidsynth (which looked
worth exploring some day when I have more time).
Play an arbitrary waveform with Pygame and NumPy
But I did find one example based on a long-obsolete Python package
called Numeric which, when rewritten to use NumPy, actually played a sound.
You can take a NumPy array and play it using a pygame.sndarray object
this way:
import pygame, pygame.sndarray
def play_for(sample_wave, ms):
"""Play the given NumPy array, as a sound, for ms milliseconds."""
sound = pygame.sndarray.make_sound(sample_wave)
sound.play(-1)
pygame.time.delay(ms)
sound.stop()
Then you just need to calculate the waveform you want to play. NumPy
can generate sine waves on its own, while scipy.signal can generate
square and sawtooth waves. Like this:
import numpy
import scipy.signal
sample_rate = 44100
def sine_wave(hz, peak, n_samples=sample_rate):
"""Compute N samples of a sine wave with given frequency and peak amplitude.
Defaults to one second.
"""
length = sample_rate / float(hz)
omega = numpy.pi * 2 / length
xvalues = numpy.arange(int(length)) * omega
onecycle = peak * numpy.sin(xvalues)
return numpy.resize(onecycle, (n_samples,)).astype(numpy.int16)
def square_wave(hz, peak, duty_cycle=.5, n_samples=sample_rate):
"""Compute N samples of a sine wave with given frequency and peak amplitude.
Defaults to one second.
"""
t = numpy.linspace(0, 1, 500 * 440/hz, endpoint=False)
wave = scipy.signal.square(2 * numpy.pi * 5 * t, duty=duty_cycle)
wave = numpy.resize(wave, (n_samples,))
return (peak / 2 * wave.astype(numpy.int16))
# Play A (440Hz) for 1 second as a sine wave:
play_for(sine_wave(440, 4096), 1000)
# Play A-440 for 1 second as a square wave:
play_for(square_wave(440, 4096), 1000)
Playing chords
That's all very well, but it's still a single tone, not a chord.
To generate a chord of two notes, you can add the waveforms for the
two notes. For instance, 440Hz is concert A, and the A one octave above
it is double the frequence, or 880 Hz. If you wanted to play a chord
consisting of those two As, you could do it like this:
play_for(sum([sine_wave(440, 4096), sine_wave(880, 4096)]), 1000)
Simple octaves aren't very interesting to listen to.
What you want is chords like major and minor triads and so forth.
If you google for chord ratios
Google helpfully gives
you a few of them right off, then links to a page with
a table of
ratios for some common chords.
For instance, the major triad ratios are listed as 4:5:6
.
What does that mean? It means that for a C-E-G triad (the first C
chord you learn in piano), the E's frequency is 5/4 of the C's
frequency, and the G is 6/4 of the C.
You can pass that list, [4, 5, 5] to a function that will calculate
the right ratios to produce the set of waveforms you need to add
to get your chord:
def make_chord(hz, ratios):
"""Make a chord based on a list of frequency ratios."""
sampling = 4096
chord = waveform(hz, sampling)
for r in ratios[1:]:
chord = sum([chord, sine_wave(hz * r / ratios[0], sampling)])
return chord
def major_triad(hz):
return make_chord(hz, [4, 5, 6])
play_for(major_triad(440), length)
Even better, you can pass in the waveform you want to use
when you're adding instruments together:
def make_chord(hz, ratios, waveform=None):
"""Make a chord based on a list of frequency ratios
using a given waveform (defaults to a sine wave).
"""
sampling = 4096
if not waveform:
waveform = sine_wave
chord = waveform(hz, sampling)
for r in ratios[1:]:
chord = sum([chord, waveform(hz * r / ratios[0], sampling)])
return chord
def major_triad(hz, waveform=None):
return make_chord(hz, [4, 5, 6], waveform)
play_for(major_triad(440, square_wave), length)
There are still some problems. For instance, sawtooth_wave() works
fine individually or for pairs of notes, but triads of sawtooths don't
play correctly. I'm guessing something about the sampling rate is
making their overtones cancel out part of the sawtooth wave. Triangle
waves (in scipy.signal, that's a sawtooth wave with rising ramp width
of 0.5) don't seem to work right even for single tones. I'm sure these
are solvable, perhaps by fiddling with the sampling rate. I'll
probably need to add graphics so I can look at the waveform for
debugging purposes.
In any case, it was a fun morning hack. Most chords work pretty well,
and it's nice to know how to to play any waveform I can generate.
The full script is here:
play_chord.py
on GitHub.
Tags: python, programming, music
[
11:29 Oct 05, 2016
More programming |
permalink to this entry |
]
Sat, 01 Oct 2016
Lately, when shooting photos with my DSLR, I've been shooting raw mode
but with a JPEG copy as well. When I triage and label my photos (with
pho and metapho), I use only the JPEG files, since they load faster
and there's no need to index both. But that means that sometimes I
delete a .jpg file while the huge .cr2 raw file is still on my disk.
I wanted some way of removing these orphaned raw files: in other words,
for every .cr2 file that doesn't have a corresponding .jpg file, delete
the .cr2.
That's an easy enough shell function to write: loop over *.cr2,
change the .cr2 extension to .jpg, check whether that file exists,
and if it doesn't, delete the .cr2.
But as I started to write the shell function, it occurred to me:
this is just the sort of magic trick zsh tends to have built in.
So I hopped on over to #zsh and asked, and in just a few minutes,
I had an answer:
rm *.cr2(e:'[[ ! -e ${REPLY%.cr2}.jpg ]]':)
Yikes! And it works! But how does it work? It's cheating to rely on people
in IRC channels without trying to understand the answer so I can solve
the next similar problem on my own.
Most of the answer is in
the zshexpn
man page, but it still took some reading and jumping around to put
the pieces together.
First, we take all files matching the initial wildcard, *.cr2
.
We're going to apply to them the filename generation code expression
in parentheses after the wildcard. (I think you need EXTENDED_GLOB set
to use that sort of parenthetical expression.)
The variable $REPLY is set to the filename the wildcard
expression matched;
so it will be set to each .cr2 filename, e.g. img001.cr2.
The expression ${REPLY%.cr2}
removes the .cr2 extension.
Then we tack on a .jpg: ${REPLY%.cr2}.jpg
.
So now we have img001.jpg.
[[ ! -e ${REPLY%.cr2}.jpg ]]
checks for the existence of
that jpg filename, just like in a shell script.
So that explains the quoted shell expression.
The final, and hardest part, is how to use that quoted expression.
That's in section 14.8.7 Glob Qualifiers.
(estring)
executes string as shell code, and the
filename will be included in the list if and only if the code returns
a zero status.
The colons -- after the e and before the closing parenthesis -- are
just separator characters. Whatever character immediately follows the
e will be taken as the separator, and anything from there to the next
instance of that separator (the second colon, in this case) is taken
as the string to execute. Colons seem to be the character to use by
convention, but you could use anything.
This is also the part of the expression responsible for setting $REPLY
to the filename being tested.
So why the quotes inside the colons? They're because some of the
substitutions being done would be evaluated too early without them:
"Note that expansions must be quoted in the string to prevent them
from being expanded before globbing is done. string is then executed
as shell code."
Whew! Complicated, but awfully handy. I know I'll have lots of other
uses for that.
One additional note: section 14.8.5, Approximate Matching, in that
manual page caught my eye. zsh can do fuzzy matches! I can't think
offhand what I need that for ... but I'm sure an idea will come to me.
Tags: zsh, shell, cmdline, imaging
[
15:28 Oct 01, 2016
More linux/cmdline |
permalink to this entry |
]